From 79338a99ea3fdd0b81dd54f9b98b7c074a9b5b3b Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 13 Jun 2025 11:52:02 +0100 Subject: [PATCH 01/39] CStr UTF-8 improvements --- boring/src/asn1.rs | 51 +++++++++++++++++++++++-------------- boring/src/bio.rs | 5 +++- boring/src/error.rs | 20 ++++++++------- boring/src/nid.rs | 8 ++++-- boring/src/ssl/callbacks.rs | 4 +-- boring/src/ssl/mod.rs | 16 ++++++++---- boring/src/string.rs | 3 +++ boring/src/x509/mod.rs | 20 ++++++--------- 8 files changed, 77 insertions(+), 50 deletions(-) diff --git a/boring/src/asn1.rs b/boring/src/asn1.rs index 891ec9f5..d9929ef7 100644 --- a/boring/src/asn1.rs +++ b/boring/src/asn1.rs @@ -63,20 +63,19 @@ foreign_type_and_impl_send_sync! { impl fmt::Display for Asn1GeneralizedTimeRef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - unsafe { - let mem_bio = match MemBio::new() { - Err(_) => return f.write_str("error"), - Ok(m) => m, - }; - let print_result = cvt(ffi::ASN1_GENERALIZEDTIME_print( - mem_bio.as_ptr(), - self.as_ptr(), - )); - match print_result { - Err(_) => f.write_str("error"), - Ok(_) => f.write_str(str::from_utf8_unchecked(mem_bio.get_buf())), - } - } + let bio = MemBio::new().ok(); + let msg = bio + .as_ref() + .and_then(|mem_bio| unsafe { + cvt(ffi::ASN1_GENERALIZEDTIME_print( + mem_bio.as_ptr(), + self.as_ptr(), + )) + .ok()?; + str::from_utf8(mem_bio.get_buf()).ok() + }) + .unwrap_or("error"); + f.write_str(msg) } } @@ -528,7 +527,20 @@ impl Asn1BitStringRef { #[corresponds(ASN1_STRING_get0_data)] #[must_use] pub fn as_slice(&self) -> &[u8] { - unsafe { slice::from_raw_parts(ASN1_STRING_get0_data(self.as_ptr() as *mut _), self.len()) } + unsafe { + let ptr = ASN1_STRING_get0_data(self.as_ptr().cast()); + if ptr.is_null() { + return &[]; + } + slice::from_raw_parts(ptr, self.len()) + } + } + + /// Returns the Asn1BitString as a str, if possible. + #[corresponds(ASN1_STRING_get0_data)] + #[must_use] + pub fn to_str(&self) -> Option<&str> { + str::from_utf8(self.as_slice()).ok() } /// Returns the number of bytes in the string. @@ -601,10 +613,11 @@ impl fmt::Display for Asn1ObjectRef { self.as_ptr(), 0, ); - match str::from_utf8(&buf[..len as usize]) { - Err(_) => fmt.write_str("error"), - Ok(s) => fmt.write_str(s), - } + fmt.write_str( + buf.get(..len as usize) + .and_then(|s| str::from_utf8(s).ok()) + .unwrap_or("error"), + ) } } } diff --git a/boring/src/bio.rs b/boring/src/bio.rs index 6566ebb8..71120606 100644 --- a/boring/src/bio.rs +++ b/boring/src/bio.rs @@ -68,7 +68,10 @@ impl MemBio { unsafe { let mut ptr = ptr::null_mut(); let len = ffi::BIO_get_mem_data(self.0, &mut ptr); - slice::from_raw_parts(ptr as *const _ as *const _, len as usize) + if ptr.is_null() { + return &[]; + } + slice::from_raw_parts(ptr.cast_const().cast(), len as usize) } } } diff --git a/boring/src/error.rs b/boring/src/error.rs index 1d44357e..d2c01db0 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -146,9 +146,9 @@ impl Error { // The memory referenced by data is only valid until that slot is overwritten // in the error stack, so we'll need to copy it off if it's dynamic let data = if flags & ffi::ERR_FLAG_STRING != 0 { - let bytes = CStr::from_ptr(data as *const _).to_bytes(); - let data = String::from_utf8_lossy(bytes).into_owned(); - Some(data.into()) + Some(Cow::Owned( + CStr::from_ptr(data.cast()).to_string_lossy().into_owned(), + )) } else { None }; @@ -214,8 +214,10 @@ impl Error { if cstr.is_null() { return None; } - let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); - str::from_utf8(bytes).ok() + CStr::from_ptr(cstr.cast()) + .to_str() + .ok() + .filter(|&msg| msg != "unknown library") } } @@ -240,8 +242,7 @@ impl Error { if cstr.is_null() { return None; } - let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); - str::from_utf8(bytes).ok() + CStr::from_ptr(cstr.cast()).to_str().ok() } } @@ -263,8 +264,9 @@ impl Error { if self.file.is_null() { return ""; } - let bytes = CStr::from_ptr(self.file as *const _).to_bytes(); - str::from_utf8(bytes).unwrap_or_default() + CStr::from_ptr(self.file.cast()) + .to_str() + .unwrap_or_default() } } diff --git a/boring/src/nid.rs b/boring/src/nid.rs index 43fc19f4..347b30f7 100644 --- a/boring/src/nid.rs +++ b/boring/src/nid.rs @@ -88,7 +88,9 @@ impl Nid { pub fn long_name(&self) -> Result<&'static str, ErrorStack> { unsafe { let nameptr = cvt_p(ffi::OBJ_nid2ln(self.0) as *mut c_char)?; - str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).map_err(ErrorStack::internal_error) + CStr::from_ptr(nameptr) + .to_str() + .map_err(ErrorStack::internal_error) } } @@ -98,7 +100,9 @@ impl Nid { pub fn short_name(&self) -> Result<&'static str, ErrorStack> { unsafe { let nameptr = cvt_p(ffi::OBJ_nid2sn(self.0) as *mut c_char)?; - str::from_utf8(CStr::from_ptr(nameptr).to_bytes()).map_err(ErrorStack::internal_error) + CStr::from_ptr(nameptr) + .to_str() + .map_err(ErrorStack::internal_error) } } diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index 8ad4ba55..fa4cd37c 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -451,14 +451,14 @@ where { // SAFETY: boring provides valid inputs. let ssl = unsafe { SslRef::from_ptr(ssl as *mut _) }; - let line = unsafe { str::from_utf8_unchecked(CStr::from_ptr(line).to_bytes()) }; + let line = unsafe { CStr::from_ptr(line).to_string_lossy() }; let callback = ssl .ssl_context() .ex_data(SslContext::cached_ex_index::()) .expect("BUG: get session callback missing"); - callback(ssl, line); + callback(ssl, &line); } pub(super) unsafe extern "C" fn raw_sign( diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 90934d06..4e320d84 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2564,7 +2564,7 @@ impl SslCipherRef { CStr::from_ptr(ptr as *const _) }; - str::from_utf8(version.to_bytes()).unwrap() + version.to_str().unwrap() } /// Returns the number of bits used for the cipher. @@ -2590,7 +2590,7 @@ impl SslCipherRef { // SSL_CIPHER_description requires a buffer of at least 128 bytes. let mut buf = [0; 128]; let ptr = ffi::SSL_CIPHER_description(self.as_ptr(), buf.as_mut_ptr(), 128); - String::from_utf8(CStr::from_ptr(ptr as *const _).to_bytes().to_vec()).unwrap() + CStr::from_ptr(ptr.cast()).to_string_lossy().into_owned() } } @@ -3216,6 +3216,8 @@ impl SslRef { } /// Returns a short string describing the state of the session. + /// + /// Returns empty string if the state wasn't valid UTF-8 #[corresponds(SSL_state_string)] #[must_use] pub fn state_string(&self) -> &'static str { @@ -3224,10 +3226,12 @@ impl SslRef { CStr::from_ptr(ptr as *const _) }; - str::from_utf8(state.to_bytes()).unwrap() + state.to_str().unwrap_or_default() } /// Returns a longer string describing the state of the session. + /// + /// Returns empty string if the state wasn't valid UTF-8 #[corresponds(SSL_state_string_long)] #[must_use] pub fn state_string_long(&self) -> &'static str { @@ -3236,7 +3240,7 @@ impl SslRef { CStr::from_ptr(ptr as *const _) }; - str::from_utf8(state.to_bytes()).unwrap() + state.to_str().unwrap_or_default() } /// Sets the host name to be sent to the server for Server Name Indication (SNI). @@ -3348,6 +3352,8 @@ impl SslRef { } /// Returns a string describing the protocol version of the session. + /// + /// This may panic if the string isn't valid UTF-8 for some reason. Use [`Self::version2`] instead. #[corresponds(SSL_get_version)] #[must_use] pub fn version_str(&self) -> &'static str { @@ -3356,7 +3362,7 @@ impl SslRef { CStr::from_ptr(ptr as *const _) }; - str::from_utf8(version.to_bytes()).unwrap() + version.to_str().unwrap() } /// Sets the minimum supported protocol version. diff --git a/boring/src/string.rs b/boring/src/string.rs index 05c401f1..7f4f5724 100644 --- a/boring/src/string.rs +++ b/boring/src/string.rs @@ -13,6 +13,9 @@ foreign_type_and_impl_send_sync! { type CType = c_char; fn drop = free; + /// # Safety + /// + /// MUST be UTF-8 pub struct OpensslString; } diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 0dbfcb15..bd4cd025 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -19,7 +19,6 @@ use std::mem; use std::net::IpAddr; use std::path::Path; use std::ptr; -use std::slice; use std::str; use std::sync::{LazyLock, Once}; @@ -1535,6 +1534,8 @@ impl X509VerifyError { } /// Return a human readable error string from the verification error. + /// + /// Returns empty string if the message was not UTF-8 #[corresponds(X509_verify_cert_error_string)] #[allow(clippy::trivially_copy_pass_by_ref)] #[must_use] @@ -1543,7 +1544,7 @@ impl X509VerifyError { unsafe { let s = ffi::X509_verify_cert_error_string(c_long::from(self.0)); - str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap() + CStr::from_ptr(s).to_str().unwrap_or_default() } } } @@ -1695,14 +1696,12 @@ impl GeneralNameRef { return None; } - let ptr = ASN1_STRING_get0_data((*self.as_ptr()).d.ia5 as *mut _); - let len = ffi::ASN1_STRING_length((*self.as_ptr()).d.ia5 as *mut _); + let asn = Asn1BitStringRef::from_ptr((*self.as_ptr()).d.ia5); - let slice = slice::from_raw_parts(ptr, len as usize); // IA5Strings are stated to be ASCII (specifically IA5). Hopefully // OpenSSL checks that when loading a certificate but if not we'll // use this instead of from_utf8_unchecked just in case. - str::from_utf8(slice).ok() + asn.to_str() } } @@ -1732,10 +1731,7 @@ impl GeneralNameRef { return None; } - let ptr = ASN1_STRING_get0_data((*self.as_ptr()).d.ip as *mut _); - let len = ffi::ASN1_STRING_length((*self.as_ptr()).d.ip as *mut _); - - Some(slice::from_raw_parts(ptr, len as usize)) + Some(Asn1BitStringRef::from_ptr((*self.as_ptr()).d.ip).as_slice()) } } } @@ -1811,8 +1807,8 @@ impl Stackable for X509Object { use crate::ffi::{X509_get0_signature, X509_getm_notAfter, X509_getm_notBefore, X509_up_ref}; use crate::ffi::{ - ASN1_STRING_get0_data, X509_ALGOR_get0, X509_REQ_get_subject_name, X509_REQ_get_version, - X509_STORE_CTX_get0_chain, X509_set1_notAfter, X509_set1_notBefore, + X509_ALGOR_get0, X509_REQ_get_subject_name, X509_REQ_get_version, X509_STORE_CTX_get0_chain, + X509_set1_notAfter, X509_set1_notBefore, }; use crate::ffi::X509_OBJECT_get0_X509; From 21f2885be30a000fdd5436fa33ec32215ddbb35e Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 26 Aug 2025 13:14:42 +0100 Subject: [PATCH 02/39] Fix swapped host/target args --- boring-sys/build/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index c40f611d..d4c54e09 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -142,7 +142,7 @@ impl Features { } impl Env { - fn from_env(target: &str, host: &str, is_fips_like: bool) -> Self { + fn from_env(host: &str, target: &str, is_fips_like: bool) -> Self { const NORMAL_PREFIX: &str = "BORING_BSSL"; const FIPS_PREFIX: &str = "BORING_BSSL_FIPS"; From a50a39fde7967a6c25ed6ee7ec2feabad69c30bf Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 26 Aug 2025 11:58:21 +0100 Subject: [PATCH 03/39] Support TARGET_CC and CC_{target} --- .github/workflows/ci.yml | 8 ++++---- boring-sys/build/config.rs | 17 ++++++++++------- boring-sys/build/main.rs | 9 +++++++++ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46477de4..3fa07a9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,8 +145,8 @@ jobs: apt_packages: gcc-arm-linux-gnueabi g++-arm-linux-gnueabi check_only: true custom_env: - CC: arm-linux-gnueabi-gcc - CXX: arm-linux-gnueabi-g++ + CC_arm-unknown-linux-gnueabi: arm-linux-gnueabi-gcc + CXX_arm-unknown-linux-gnueabi: arm-linux-gnueabi-g++ CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABI_LINKER: arm-linux-gnueabi-g++ - thing: aarch64-linux target: aarch64-unknown-linux-gnu @@ -155,8 +155,8 @@ jobs: apt_packages: crossbuild-essential-arm64 check_only: true custom_env: - CC: aarch64-linux-gnu-gcc - CXX: aarch64-linux-gnu-g++ + CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc + CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-g++ - thing: arm64-macos target: aarch64-apple-darwin diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index d4c54e09..55efcd55 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -36,6 +36,9 @@ pub(crate) struct Env { pub(crate) android_ndk_home: Option, pub(crate) cmake_toolchain_file: Option, pub(crate) cpp_runtime_lib: Option, + /// C compiler (ignored if using FIPS) + pub(crate) cc: Option, + pub(crate) cxx: Option, pub(crate) docs_rs: bool, } @@ -146,18 +149,15 @@ impl Env { const NORMAL_PREFIX: &str = "BORING_BSSL"; const FIPS_PREFIX: &str = "BORING_BSSL_FIPS"; + let var_prefix = if host == target { "HOST" } else { "TARGET" }; let target_with_underscores = target.replace('-', "_"); - // Logic stolen from cmake-rs. - let target_var = |name: &str| { - let kind = if host == target { "HOST" } else { "TARGET" }; - - // TODO(rmehra): look for just `name` first, as most people just set that + let target_only_var = |name: &str| { var(&format!("{name}_{target}")) .or_else(|| var(&format!("{name}_{target_with_underscores}"))) - .or_else(|| var(&format!("{kind}_{name}"))) - .or_else(|| var(name)) + .or_else(|| var(&format!("{var_prefix}_{name}"))) }; + let target_var = |name: &str| target_only_var(name).or_else(|| var(name)); let boringssl_var = |name: &str| { // The passed name is the non-fips version of the environment variable, @@ -186,6 +186,9 @@ impl Env { android_ndk_home: target_var("ANDROID_NDK_HOME").map(Into::into), cmake_toolchain_file: target_var("CMAKE_TOOLCHAIN_FILE").map(Into::into), cpp_runtime_lib: target_var("BORING_BSSL_RUST_CPPLIB"), + // matches the `cc` crate + cc: target_only_var("CC"), + cxx: target_only_var("CXX"), docs_rs: var("DOCS_RS").is_some(), } } diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 1342e58e..421c086a 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -216,6 +216,15 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { .define("CMAKE_ASM_COMPILER_TARGET", &config.target); } + if !config.features.fips { + if let Some(cc) = &config.env.cc { + boringssl_cmake.define("CMAKE_C_COMPILER", cc); + } + if let Some(cxx) = &config.env.cxx { + boringssl_cmake.define("CMAKE_CXX_COMPILER", cxx); + } + } + if let Some(sysroot) = &config.env.sysroot { boringssl_cmake.define("CMAKE_SYSROOT", sysroot); } From 4814eb8547c4e5ea35b127aba525ca848db048df Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 26 Sep 2025 12:10:48 +0100 Subject: [PATCH 04/39] Ensure rustfmt and clippy are available --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fa07a9b..92ffab40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,8 +18,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install Rust - run: rustup update stable && rustup default stable + - name: Install Rustfmt + run: rustup default stable && rustup component add rustfmt - name: Check formatting run: cargo fmt --all -- --check @@ -31,7 +31,7 @@ jobs: with: submodules: 'recursive' - name: Install Rust - run: rustup update stable && rustup default stable + run: rustup update --no-self-update stable && rustup default stable && rustup component add clippy - name: Get rust version id: rust-version run: echo "::set-output name=version::$(rustc --version)" From fa9df8081db0c1235fb60e5c4e919c392068bc15 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 26 Sep 2025 12:17:25 +0100 Subject: [PATCH 05/39] Deprecated GHA feature --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92ffab40..a8f17085 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,8 @@ jobs: run: rustup update --no-self-update stable && rustup default stable && rustup component add clippy - name: Get rust version id: rust-version - run: echo "::set-output name=version::$(rustc --version)" + run: | + echo "version=$(rustc --version)" >> $GITHUB_OUTPUT - name: Cache cargo index uses: actions/cache@v4 with: From 9bad96e48b902460aeacfa7f8020ece6c007867d Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 26 Sep 2025 11:10:22 +0100 Subject: [PATCH 06/39] Style nits --- boring-sys/build/config.rs | 8 +++---- boring-sys/build/main.rs | 6 +++--- boring/examples/mk_certs.rs | 4 ++-- boring/src/dh.rs | 2 +- boring/src/ssl/async_callbacks.rs | 8 +++---- boring/src/ssl/callbacks.rs | 2 +- boring/src/ssl/mod.rs | 30 +++++++++++--------------- boring/src/string.rs | 2 +- boring/src/x509/mod.rs | 4 ++-- boring/src/x509/tests/trusted_first.rs | 2 +- tokio-boring/src/bridge.rs | 2 +- 11 files changed, 33 insertions(+), 37 deletions(-) diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 55efcd55..5d93eda4 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -54,10 +54,10 @@ impl Config { let features = Features::from_env(); let env = Env::from_env(&host, &target, features.is_fips_like()); - let mut is_bazel = false; - if let Some(src_path) = &env.source_path { - is_bazel = src_path.join("src").exists(); - } + let is_bazel = env + .source_path + .as_ref() + .is_some_and(|path| path.join("src").exists()); let config = Self { manifest_dir, diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 421c086a..1270311f 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -152,7 +152,7 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf { /// /// MSVC generator on Windows place static libs in a target sub-folder, /// so adjust library location based on platform and build target. -/// See issue: https://github.com/alexcrichton/cmake-rs/issues/18 +/// See issue: fn get_boringssl_platform_output_path(config: &Config) -> String { if config.target.ends_with("-msvc") { // Code under this branch should match the logic in cmake-rs @@ -193,7 +193,7 @@ fn get_boringssl_platform_output_path(config: &Config) -> String { } } -/// Returns a new cmake::Config for building BoringSSL. +/// Returns a new `cmake::Config` for building BoringSSL. /// /// It will add platform-specific parameters if needed. fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { @@ -340,7 +340,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { boringssl_cmake } -/// Verify that the toolchains match https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3678.pdf +/// Verify that the toolchains match /// See "Installation Instructions" under section 12.1. // TODO: maybe this should also verify the Go and Ninja versions? But those haven't been an issue in practice ... fn verify_fips_clang_version() -> (&'static str, &'static str) { diff --git a/boring/examples/mk_certs.rs b/boring/examples/mk_certs.rs index a130dee6..1fc4993d 100644 --- a/boring/examples/mk_certs.rs +++ b/boring/examples/mk_certs.rs @@ -147,7 +147,7 @@ fn real_main() -> Result<(), ErrorStack> { match ca_cert.issued(&cert) { Ok(()) => println!("Certificate verified!"), Err(ver_err) => println!("Failed to verify certificate: {ver_err}"), - }; + } Ok(()) } @@ -156,5 +156,5 @@ fn main() { match real_main() { Ok(()) => println!("Finished."), Err(e) => println!("Error: {e}"), - }; + } } diff --git a/boring/src/dh.rs b/boring/src/dh.rs index c8449d86..8e0c68fb 100644 --- a/boring/src/dh.rs +++ b/boring/src/dh.rs @@ -32,7 +32,7 @@ where } to_der! { - /// Serializes the parameters into a DER-encoded PKCS#3 DHparameter structure. + /// Serializes the parameters into a DER-encoded PKCS#3 `DHparameter` structure. #[corresponds(i2d_DHparams)] params_to_der, ffi::i2d_DHparams diff --git a/boring/src/ssl/async_callbacks.rs b/boring/src/ssl/async_callbacks.rs index e4bdf846..4ab18d11 100644 --- a/boring/src/ssl/async_callbacks.rs +++ b/boring/src/ssl/async_callbacks.rs @@ -95,7 +95,7 @@ impl SslContextBuilder { let finish = fut_result.or(Err(SelectCertError::ERROR))?; finish(client_hello).or(Err(SelectCertError::ERROR)) - }) + }); } /// Configures a custom private key method on the context. @@ -144,7 +144,7 @@ impl SslContextBuilder { } }; - self.set_get_session_callback(async_callback) + self.set_get_session_callback(async_callback); } /// Configures certificate verification. @@ -167,7 +167,7 @@ impl SslContextBuilder { where F: Fn(&mut SslRef) -> Result + Send + Sync + 'static, { - self.set_custom_verify_callback(mode, async_custom_verify_callback(callback)) + self.set_custom_verify_callback(mode, async_custom_verify_callback(callback)); } } @@ -176,7 +176,7 @@ impl SslRef { where F: Fn(&mut SslRef) -> Result + Send + Sync + 'static, { - self.set_custom_verify_callback(mode, async_custom_verify_callback(callback)) + self.set_custom_verify_callback(mode, async_custom_verify_callback(callback)); } /// Sets the task waker to be used in async callbacks installed on this `Ssl`. diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index fa4cd37c..f618e591 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -399,7 +399,7 @@ pub(super) unsafe extern "C" fn raw_remove_session( .ex_data(SslContext::cached_ex_index::()) .expect("BUG: remove session callback missing"); - callback(ctx, session) + callback(ctx, session); } type DataPtr = *const c_uchar; diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 4e320d84..9cd40405 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1841,7 +1841,7 @@ impl SslContextBuilder { + Sync + Send, { - self.set_psk_client_callback(callback) + self.set_psk_client_callback(callback); } /// Sets the callback for providing an identity and pre-shared key for a TLS-PSK server. @@ -3217,7 +3217,7 @@ impl SslRef { /// Returns a short string describing the state of the session. /// - /// Returns empty string if the state wasn't valid UTF-8 + /// Returns empty string if the state wasn't valid UTF-8. #[corresponds(SSL_state_string)] #[must_use] pub fn state_string(&self) -> &'static str { @@ -3231,7 +3231,7 @@ impl SslRef { /// Returns a longer string describing the state of the session. /// - /// Returns empty string if the state wasn't valid UTF-8 + /// Returns empty string if the state wasn't valid UTF-8. #[corresponds(SSL_state_string_long)] #[must_use] pub fn state_string_long(&self) -> &'static str { @@ -4034,10 +4034,11 @@ impl MidHandshakeSslStream { Ok(self.stream) } else { self.error = self.stream.make_error(ret); - match self.error.would_block() { - true => Err(HandshakeError::WouldBlock(self)), - false => Err(HandshakeError::Failure(self)), - } + Err(if self.error.would_block() { + HandshakeError::WouldBlock(self) + } else { + HandshakeError::Failure(self) + }) } } } @@ -4461,16 +4462,11 @@ where Ok(stream) } else { let error = stream.make_error(ret); - match error.would_block() { - true => Err(HandshakeError::WouldBlock(MidHandshakeSslStream { - stream, - error, - })), - false => Err(HandshakeError::Failure(MidHandshakeSslStream { - stream, - error, - })), - } + Err(if error.would_block() { + HandshakeError::WouldBlock(MidHandshakeSslStream { stream, error }) + } else { + HandshakeError::Failure(MidHandshakeSslStream { stream, error }) + }) } } } diff --git a/boring/src/string.rs b/boring/src/string.rs index 7f4f5724..4527f0d7 100644 --- a/boring/src/string.rs +++ b/boring/src/string.rs @@ -15,7 +15,7 @@ foreign_type_and_impl_send_sync! { /// # Safety /// - /// MUST be UTF-8 + /// MUST be UTF-8. pub struct OpensslString; } diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index bd4cd025..8bf96d33 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -864,7 +864,7 @@ impl fmt::Debug for X509 { if let Ok(public_key) = &self.public_key() { debug_struct.field("public_key", public_key); - }; + } // TODO: Print extensions once they are supported on the X509 struct. debug_struct.finish() @@ -1535,7 +1535,7 @@ impl X509VerifyError { /// Return a human readable error string from the verification error. /// - /// Returns empty string if the message was not UTF-8 + /// Returns empty string if the message was not UTF-8. #[corresponds(X509_verify_cert_error_string)] #[allow(clippy::trivially_copy_pass_by_ref)] #[must_use] diff --git a/boring/src/x509/tests/trusted_first.rs b/boring/src/x509/tests/trusted_first.rs index d79ff2e3..b5e714b5 100644 --- a/boring/src/x509/tests/trusted_first.rs +++ b/boring/src/x509/tests/trusted_first.rs @@ -61,7 +61,7 @@ fn test_verify_cert() { Ok(()), verify(&leaf, &[&root1], &[&intermediate, &root1_cross], |param| { param.clear_flags(X509Flags::TRUSTED_FIRST) - },) + }) ); } diff --git a/tokio-boring/src/bridge.rs b/tokio-boring/src/bridge.rs index 62ed7729..5fdd1c30 100644 --- a/tokio-boring/src/bridge.rs +++ b/tokio-boring/src/bridge.rs @@ -22,7 +22,7 @@ impl AsyncStreamBridge { } pub(crate) fn set_waker(&mut self, ctx: Option<&mut Context<'_>>) { - self.waker = ctx.map(|ctx| ctx.waker().clone()) + self.waker = ctx.map(|ctx| ctx.waker().clone()); } /// # Panics From 3116032a83a891f9495698bf0692ccc59ceef6e2 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 26 Sep 2025 11:45:40 +0100 Subject: [PATCH 07/39] Skip Rust version detection for bindgen --- Cargo.toml | 1 - boring-sys/Cargo.toml | 2 +- boring-sys/build/main.rs | 10 +++------- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d693db43..d27cf8db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,5 +48,4 @@ openssl-macros = "0.1.1" tower = "0.4" tower-layer = "0.3" tower-service = "0.3" -autocfg = "1.3.0" brotli = "6.0" diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 5cd8e214..166d62f5 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -13,6 +13,7 @@ build = "build/main.rs" readme = "README.md" categories = ["cryptography", "external-ffi-bindings"] edition = { workspace = true } +rust-version = "1.77" include = [ "/*.md", "/*.toml", @@ -89,7 +90,6 @@ pq-experimental = [] underscore-wildcards = [] [build-dependencies] -autocfg = { workspace = true } bindgen = { workspace = true } cmake = { workspace = true } fs_extra = { workspace = true } diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 1270311f..cc85532b 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -741,12 +741,8 @@ fn generate_bindings(config: &Config) { } }); - // bindgen 0.70 replaced the run-time layout tests with compile-time ones, - // but they depend on std::mem::offset_of, stabilized in 1.77. - let supports_layout_tests = autocfg::new().probe_rustc_version(1, 77); - let Ok(target_rust_version) = bindgen::RustTarget::stable(68, 0) else { - panic!("bindgen does not recognize target rust version"); - }; + let target_rust_version = + bindgen::RustTarget::stable(77, 0).expect("bindgen does not recognize target rust version"); let mut builder = bindgen::Builder::default() .rust_target(target_rust_version) // bindgen MSRV is 1.70, so this is enough @@ -762,7 +758,7 @@ fn generate_bindings(config: &Config) { .generate_comments(true) .fit_macro_constants(false) .size_t_is_usize(true) - .layout_tests(supports_layout_tests) + .layout_tests(config.env.debug.is_some()) .prepend_enum_name(true) .blocklist_type("max_align_t") // Not supported by bindgen on all targets, not used by BoringSSL .clang_args(get_extra_clang_args_for_bindgen(config)) From c3f33f0ea125a04dd0a335038272a8c28458623e Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 26 Sep 2025 11:46:43 +0100 Subject: [PATCH 08/39] Upgrade deps --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d27cf8db..451b2d99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,12 @@ boring = { version = "4.19.0", path = "./boring" } tokio-boring = { version = "4.19.0", path = "./tokio-boring" } bindgen = { version = "0.72.0", default-features = false, features = ["runtime"] } +bitflags = "2.9" +brotli = "8.0" bytes = "1" -cmake = "0.1.18" +cmake = "0.1.54" fs_extra = "1.3.0" fslock = "0.2" -bitflags = "2.4" foreign-types = "0.5" libc = "0.2" hex = "0.4" @@ -48,4 +49,3 @@ openssl-macros = "0.1.1" tower = "0.4" tower-layer = "0.3" tower-service = "0.3" -brotli = "6.0" From b4bf6013946f7e09cd816867f3d2c4a267f33b33 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Fri, 26 Sep 2025 07:32:00 +0100 Subject: [PATCH 09/39] Remove support for Hyper v0 --- .github/workflows/ci.yml | 2 - Cargo.toml | 2 - hyper-boring/Cargo.toml | 18 +- hyper-boring/src/lib.rs | 7 +- hyper-boring/src/v0.rs | 345 --------------------------------------- hyper-boring/src/v1.rs | 3 - hyper-boring/tests/v0.rs | 156 ------------------ hyper-boring/tests/v1.rs | 4 +- 8 files changed, 7 insertions(+), 530 deletions(-) delete mode 100644 hyper-boring/src/v0.rs delete mode 100644 hyper-boring/tests/v0.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8f17085..110d8ba7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -371,5 +371,3 @@ jobs: name: Run `rpk,underscore-wildcards` tests - run: cargo test --features pq-experimental,rpk,underscore-wildcards name: Run `pq-experimental,rpk,underscore-wildcards` tests - - run: cargo test -p hyper-boring --features hyper1 - name: Run hyper 1.0 tests for hyper-boring diff --git a/Cargo.toml b/Cargo.toml index 451b2d99..8fd3835e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,10 +40,8 @@ anyhow = "1" antidote = "1.0.0" http = "1" http-body-util = "0.1.2" -http_old = { package = "http", version = "0.2" } hyper = "1" hyper-util = "0.1.6" -hyper_old = { package = "hyper", version = "0.14", default-features = false } linked_hash_set = "0.1" openssl-macros = "0.1.1" tower = "0.4" diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 91f74424..77a8a788 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -16,10 +16,6 @@ features = ["pq-experimental"] rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["runtime"] - -runtime = ["hyper_old/runtime"] - # Use a FIPS-validated version of boringssl. fips = ["tokio-boring/fips"] @@ -39,29 +35,23 @@ fips-link-precompiled = ["tokio-boring/fips-link-precompiled"] # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["tokio-boring/pq-experimental"] -# Enable Hyper 1 support -hyper1 = ["dep:http", "dep:hyper", "dep:hyper-util", "dep:tower-service"] - [dependencies] antidote = { workspace = true } -http = { workspace = true, optional = true } -http_old = { workspace = true } -hyper = { workspace = true, optional = true } -hyper-util = { workspace = true, optional = true, features = ["client", "client-legacy"] } -hyper_old = { workspace = true, features = ["client"] } +http = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true, features = ["client", "client-legacy"] } linked_hash_set = { workspace = true } boring = { workspace = true } tokio = { workspace = true } tokio-boring = { workspace = true } tower-layer = { workspace = true } -tower-service = { workspace = true, optional = true } +tower-service = { workspace = true } [dev-dependencies] bytes = { workspace = true } http-body-util = { workspace = true } hyper-util = { workspace = true, features = ["http1", "http2", "service", "tokio"] } hyper = { workspace = true, features = ["server"] } -hyper_old = { workspace = true, features = [ "full" ] } tokio = { workspace = true, features = [ "full" ] } tower = { workspace = true, features = ["util"] } futures = { workspace = true } diff --git a/hyper-boring/src/lib.rs b/hyper-boring/src/lib.rs index 1822e135..0e1f2b17 100644 --- a/hyper-boring/src/lib.rs +++ b/hyper-boring/src/lib.rs @@ -11,12 +11,9 @@ use std::sync::LazyLock; use tokio_boring::SslStream; mod cache; -mod v0; -/// Hyper 1 support. -#[cfg(feature = "hyper1")] -pub mod v1; +mod v1; -pub use self::v0::*; +pub use self::v1::*; fn key_index() -> Result, ErrorStack> { static IDX: LazyLock> = LazyLock::new(|| Ssl::new_ex_index().unwrap()); diff --git a/hyper-boring/src/v0.rs b/hyper-boring/src/v0.rs deleted file mode 100644 index 03368d32..00000000 --- a/hyper-boring/src/v0.rs +++ /dev/null @@ -1,345 +0,0 @@ -use crate::cache::{SessionCache, SessionKey}; -use crate::{key_index, HttpsLayerSettings, MaybeHttpsStream}; -use antidote::Mutex; -use boring::error::ErrorStack; -use boring::ssl::{ - ConnectConfiguration, Ssl, SslConnector, SslConnectorBuilder, SslMethod, SslRef, - SslSessionCacheMode, -}; -use http_old::uri::Scheme; -use hyper_old::client::connect::{Connected, Connection}; -use hyper_old::client::HttpConnector; -use hyper_old::service::Service; -use hyper_old::Uri; -use std::error::Error; -use std::future::Future; -use std::net; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::{fmt, io}; -use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use tower_layer::Layer; - -/// A Connector using BoringSSL to support `http` and `https` schemes. -#[derive(Clone)] -pub struct HttpsConnector { - http: T, - inner: Inner, -} - -#[cfg(feature = "runtime")] -impl HttpsConnector { - /// Creates a a new `HttpsConnector` using default settings. - /// - /// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both - /// HTTP/2 and HTTP/1.1. - /// - /// Requires the `runtime` Cargo feature. - pub fn new() -> Result, ErrorStack> { - let mut http = HttpConnector::new(); - http.enforce_http(false); - - HttpsLayer::new().map(|l| l.layer(http)) - } -} - -impl HttpsConnector -where - S: Service + Send, - S::Error: Into>, - S::Future: Unpin + Send + 'static, - T: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static, -{ - /// Creates a new `HttpsConnector`. - /// - /// The session cache configuration of `ssl` will be overwritten. - pub fn with_connector( - http: S, - ssl: SslConnectorBuilder, - ) -> Result, ErrorStack> { - HttpsLayer::with_connector(ssl).map(|l| l.layer(http)) - } - - /// Registers a callback which can customize the configuration of each connection. - /// - /// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`), - /// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`] - /// instead. - pub fn set_callback(&mut self, callback: F) - where - F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.callback = Some(Arc::new(callback)); - } - - /// Registers a callback which can customize the `Ssl` of each connection. - pub fn set_ssl_callback(&mut self, callback: F) - where - F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.ssl_callback = Some(Arc::new(callback)); - } -} - -/// A layer which wraps services in an `HttpsConnector`. -pub struct HttpsLayer { - inner: Inner, -} - -#[derive(Clone)] -struct Inner { - ssl: SslConnector, - cache: Arc>, - callback: Option, - ssl_callback: Option, -} - -type Callback = - Arc Result<(), ErrorStack> + Sync + Send>; -type SslCallback = Arc Result<(), ErrorStack> + Sync + Send>; - -impl HttpsLayer { - /// Creates a new `HttpsLayer` with default settings. - /// - /// ALPN is configured to support both HTTP/1 and HTTP/1.1. - pub fn new() -> Result { - let mut ssl = SslConnector::builder(SslMethod::tls())?; - - ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?; - - Self::with_connector(ssl) - } - - /// Creates a new `HttpsLayer`. - /// - /// The session cache configuration of `ssl` will be overwritten. - pub fn with_connector(ssl: SslConnectorBuilder) -> Result { - Self::with_connector_and_settings(ssl, Default::default()) - } - - /// Creates a new `HttpsLayer` with settings - pub fn with_connector_and_settings( - mut ssl: SslConnectorBuilder, - settings: HttpsLayerSettings, - ) -> Result { - let cache = Arc::new(Mutex::new(SessionCache::with_capacity( - settings.session_cache_capacity, - ))); - - ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT); - - ssl.set_new_session_callback({ - let cache = cache.clone(); - move |ssl, session| { - if let Some(key) = key_index().ok().and_then(|idx| ssl.ex_data(idx)) { - cache.lock().insert(key.clone(), session); - } - } - }); - - Ok(HttpsLayer { - inner: Inner { - ssl: ssl.build(), - cache, - callback: None, - ssl_callback: None, - }, - }) - } - - /// Registers a callback which can customize the configuration of each connection. - /// - /// Unsuitable to change verify hostflags (with `config.param_mut().set_hostflags(…)`), - /// as they are reset after the callback is executed. Use [`Self::set_ssl_callback`] - /// instead. - pub fn set_callback(&mut self, callback: F) - where - F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.callback = Some(Arc::new(callback)); - } - - /// Registers a callback which can customize the `Ssl` of each connection. - pub fn set_ssl_callback(&mut self, callback: F) - where - F: Fn(&mut SslRef, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send, - { - self.inner.ssl_callback = Some(Arc::new(callback)); - } -} - -impl Layer for HttpsLayer { - type Service = HttpsConnector; - - fn layer(&self, inner: S) -> HttpsConnector { - HttpsConnector { - http: inner, - inner: self.inner.clone(), - } - } -} - -impl Inner { - fn setup_ssl(&self, uri: &Uri, host: &str) -> Result { - let mut conf = self.ssl.configure()?; - - if let Some(ref callback) = self.callback { - callback(&mut conf, uri)?; - } - - let key = SessionKey { - host: host.to_string(), - port: uri.port_u16().unwrap_or(443), - }; - - if let Some(session) = self.cache.lock().get(&key) { - unsafe { - conf.set_session(&session)?; - } - } - - let idx = key_index()?; - conf.set_ex_data(idx, key); - - let mut ssl = conf.into_ssl(host)?; - - if let Some(ref ssl_callback) = self.ssl_callback { - ssl_callback(&mut ssl, uri)?; - } - - Ok(ssl) - } -} - -impl Service for HttpsConnector -where - S: Service + Send, - S::Error: Into>, - S::Future: Unpin + Send + 'static, - S::Response: AsyncRead + AsyncWrite + Connection + Unpin + fmt::Debug + Sync + Send + 'static, -{ - type Response = MaybeHttpsStream; - type Error = Box; - #[allow(clippy::type_complexity)] - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.http.poll_ready(cx).map_err(Into::into) - } - - fn call(&mut self, uri: Uri) -> Self::Future { - let is_tls_scheme = uri - .scheme() - .map(|s| s == &Scheme::HTTPS || s.as_str() == "wss") - .unwrap_or(false); - - let tls_setup = if is_tls_scheme { - Some((self.inner.clone(), uri.clone())) - } else { - None - }; - - let connect = self.http.call(uri); - - let f = async { - let conn = connect.await.map_err(Into::into)?; - - let (inner, uri) = match tls_setup { - Some((inner, uri)) => (inner, uri), - None => return Ok(MaybeHttpsStream::Http(conn)), - }; - - let mut host = uri.host().ok_or("URI missing host")?; - - // If `host` is an IPv6 address, we must strip away the square brackets that surround - // it (otherwise, boring will fail to parse the host as an IP address, eventually - // causing the handshake to fail due a hostname verification error). - if !host.is_empty() { - let last = host.len() - 1; - let mut chars = host.chars(); - - if (chars.next(), chars.last()) == (Some('['), Some(']')) - && host[1..last].parse::().is_ok() - { - host = &host[1..last]; - } - } - - let ssl = inner.setup_ssl(&uri, host)?; - let stream = tokio_boring::SslStreamBuilder::new(ssl, conn) - .connect() - .await?; - - Ok(MaybeHttpsStream::Https(stream)) - }; - - Box::pin(f) - } -} - -impl Connection for MaybeHttpsStream -where - T: Connection, -{ - fn connected(&self) -> Connected { - match self { - MaybeHttpsStream::Http(s) => s.connected(), - MaybeHttpsStream::Https(s) => { - let mut connected = s.get_ref().connected(); - - if s.ssl().selected_alpn_protocol() == Some(b"h2") { - connected = connected.negotiated_h2(); - } - - connected - } - } - } -} - -impl AsyncRead for MaybeHttpsStream -where - T: AsyncRead + AsyncWrite + Unpin, -{ - fn poll_read( - mut self: Pin<&mut Self>, - ctx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_read(ctx, buf), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_read(ctx, buf), - } - } -} - -impl AsyncWrite for MaybeHttpsStream -where - T: AsyncRead + AsyncWrite + Unpin, -{ - fn poll_write( - mut self: Pin<&mut Self>, - ctx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_write(ctx, buf), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_write(ctx, buf), - } - } - - fn poll_flush(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_flush(ctx), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_flush(ctx), - } - } - - fn poll_shutdown(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { - match &mut *self { - MaybeHttpsStream::Http(s) => Pin::new(s).poll_shutdown(ctx), - MaybeHttpsStream::Https(s) => Pin::new(s).poll_shutdown(ctx), - } - } -} diff --git a/hyper-boring/src/v1.rs b/hyper-boring/src/v1.rs index e1f9a43d..f4cb0168 100644 --- a/hyper-boring/src/v1.rs +++ b/hyper-boring/src/v1.rs @@ -29,14 +29,11 @@ pub struct HttpsConnector { inner: Inner, } -#[cfg(feature = "runtime")] impl HttpsConnector { /// Creates a a new `HttpsConnector` using default settings. /// /// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both /// HTTP/2 and HTTP/1.1. - /// - /// Requires the `runtime` Cargo feature. pub fn new() -> Result, ErrorStack> { let mut http = HttpConnector::new(); http.enforce_http(false); diff --git a/hyper-boring/tests/v0.rs b/hyper-boring/tests/v0.rs deleted file mode 100644 index f52e1851..00000000 --- a/hyper-boring/tests/v0.rs +++ /dev/null @@ -1,156 +0,0 @@ -use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod}; -use futures::StreamExt; -use hyper_boring::HttpsConnector; -use hyper_old::client::HttpConnector; -use hyper_old::server::conn::Http; -use hyper_old::{service, Response}; -use hyper_old::{Body, Client}; -use std::convert::Infallible; -use std::{io, iter}; -use tokio::net::TcpListener; - -#[tokio::test] -#[cfg(feature = "runtime")] -async fn google() { - let ssl = HttpsConnector::new().unwrap(); - let client = Client::builder() - .pool_max_idle_per_host(0) - .build::<_, Body>(ssl); - - for _ in 0..3 { - let resp = client - .get("https://www.google.com".parse().unwrap()) - .await - .expect("connection should succeed"); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} - } -} - -#[tokio::test] -async fn localhost() { - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let port = addr.port(); - - let server = async move { - let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - acceptor.set_session_id_context(b"test").unwrap(); - acceptor - .set_private_key_file("tests/test/key.pem", SslFiletype::PEM) - .unwrap(); - acceptor - .set_certificate_chain_file("tests/test/cert.pem") - .unwrap(); - let acceptor = acceptor.build(); - - for _ in 0..3 { - let stream = listener.accept().await.unwrap().0; - let stream = tokio_boring::accept(&acceptor, stream).await.unwrap(); - - let service = - service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) }); - - Http::new() - .http1_keep_alive(false) - .serve_connection(stream, service) - .await - .unwrap(); - } - }; - tokio::spawn(server); - - let resolver = - tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) }); - - let mut connector = HttpConnector::new_with_resolver(resolver); - - connector.enforce_http(false); - - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); - - ssl.set_ca_file("tests/test/root-ca.pem").unwrap(); - - use std::fs::File; - use std::io::Write; - - let file = File::create("../target/keyfile.log").unwrap(); - ssl.set_keylog_callback(move |_, line| { - let _ = writeln!(&file, "{line}"); - }); - - let ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); - let client = Client::builder().build::<_, Body>(ssl); - - for _ in 0..3 { - let resp = client - .get(format!("https://foobar.com:{port}").parse().unwrap()) - .await - .unwrap(); - assert!(resp.status().is_success(), "{}", resp.status()); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} - } -} - -#[tokio::test] -async fn alpn_h2() { - use boring::ssl::{self, AlpnError}; - - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let addr = listener.local_addr().unwrap(); - let port = addr.port(); - - let server = async move { - let mut acceptor = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap(); - acceptor - .set_certificate_chain_file("tests/test/cert.pem") - .unwrap(); - acceptor - .set_private_key_file("tests/test/key.pem", SslFiletype::PEM) - .unwrap(); - acceptor.set_alpn_select_callback(|_, client| { - ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK) - }); - let acceptor = acceptor.build(); - - let stream = listener.accept().await.unwrap().0; - let stream = tokio_boring::accept(&acceptor, stream).await.unwrap(); - assert_eq!(stream.ssl().selected_alpn_protocol().unwrap(), b"h2"); - - let service = - service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) }); - - Http::new() - .http2_only(true) - .serve_connection(stream, service) - .await - .unwrap(); - }; - tokio::spawn(server); - - let resolver = - tower::service_fn(move |_name| async move { Ok::<_, Infallible>(iter::once(addr)) }); - - let mut connector = HttpConnector::new_with_resolver(resolver); - - connector.enforce_http(false); - - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); - - ssl.set_ca_file("tests/test/root-ca.pem").unwrap(); - - let mut ssl = HttpsConnector::with_connector(connector, ssl).unwrap(); - - ssl.set_ssl_callback(|ssl, _| ssl.set_alpn_protos(b"\x02h2\x08http/1.1")); - - let client = Client::builder().build::<_, Body>(ssl); - - let resp = client - .get(format!("https://foobar.com:{port}").parse().unwrap()) - .await - .unwrap(); - assert!(resp.status().is_success(), "{}", resp.status()); - let mut body = resp.into_body(); - while body.next().await.transpose().unwrap().is_some() {} -} diff --git a/hyper-boring/tests/v1.rs b/hyper-boring/tests/v1.rs index 441caea6..4082d2ce 100644 --- a/hyper-boring/tests/v1.rs +++ b/hyper-boring/tests/v1.rs @@ -1,11 +1,9 @@ -#![cfg(feature = "hyper1")] - use boring::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod}; use bytes::Bytes; use futures::StreamExt; use http_body_util::{BodyStream, Empty}; use hyper::{service, Response}; -use hyper_boring::v1::HttpsConnector; +use hyper_boring::HttpsConnector; use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::Client; use hyper_util::rt::{TokioExecutor, TokioIo}; From 974c3d2db0e715df4894ce4b57ea4b276fa93e5f Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 13 Jun 2025 13:56:37 +0100 Subject: [PATCH 10/39] Ensure that ERR_LIB type can be named --- boring-sys/build/main.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index cc85532b..be9a3fa1 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -810,7 +810,24 @@ fn generate_bindings(config: &Config) { } let bindings = builder.generate().expect("Unable to generate bindings"); + let mut source_code = Vec::new(); bindings - .write_to_file(config.out_dir.join("bindings.rs")) - .expect("Couldn't write bindings!"); + .write(Box::new(&mut source_code)) + .expect("Couldn't serialize bindings!"); + ensure_err_lib_enum_is_named(&mut source_code); + fs::write(config.out_dir.join("bindings.rs"), source_code).expect("Couldn't write bindings!"); +} + +/// err.h has anonymous `enum { ERR_LIB_NONE = 1 }`, which makes a dodgy `_bindgen_ty_1` name +fn ensure_err_lib_enum_is_named(source_code: &mut Vec) { + let src = String::from_utf8_lossy(source_code); + let enum_type = src + .split_once("ERR_LIB_SSL:") + .and_then(|(_, def)| Some(def.split_once("=")?.0)) + .unwrap_or("_bindgen_ty_1"); + + source_code.extend_from_slice( + format!("\n/// Newtype for [`ERR_LIB_SSL`] constants\npub use {enum_type} as ErrLib;\n") + .as_bytes(), + ); } From 78b8ceaf10fd67126e0f30abd8a3f45aca6cab19 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 13 Jun 2025 15:34:02 +0100 Subject: [PATCH 11/39] Add more reliable library_reason() --- boring/src/error.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index d2c01db0..60d8d4f5 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -27,6 +27,8 @@ use std::str; use crate::ffi; +pub use crate::ffi::ErrLib; + /// Collection of [`Error`]s from OpenSSL. /// /// [`Error`]: struct.Error.html @@ -194,11 +196,23 @@ impl Error { } } + /// Get `{lib}_R_{reason}` reason code for the given library, or `None` if the error is from a different library. + /// + /// Libraries are identified by [`ERR_LIB_{name}`(ffi::ERR_LIB_SSL) constants. + #[inline] + #[must_use] + #[track_caller] + pub fn library_reason(&self, library_code: ErrLib) -> Option { + debug_assert!(library_code.0 < ffi::ERR_NUM_LIBS.0); + (self.library_code() == library_code.0 as c_int).then_some(self.reason_code()) + } + /// Returns a raw OpenSSL **packed** error code for this error, which **can't be reliably compared to any error constant**. /// - /// Use [`Error::library_code()`] and [`Error::reason_code()`] instead. + /// Use [`Error::library_code()`] and [`Error::library_reason()`] instead. /// Packed error codes are different than [SSL error codes](crate::ssl::ErrorCode). #[must_use] + #[deprecated(note = "use library_reason() to compare error codes")] pub fn code(&self) -> c_uint { self.code } @@ -223,7 +237,7 @@ impl Error { /// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`). /// - /// Error [reason codes](Error::reason_code) are not globally unique, but scoped to each library. + /// Error [reason codes](Error::library_reason) are not globally unique, but scoped to each library. #[must_use] pub fn library_code(&self) -> c_int { ffi::ERR_GET_LIB(self.code) @@ -249,6 +263,7 @@ impl Error { /// Returns [library-specific](Error::library_code) reason code corresponding to some of the `{lib}_R_{reason}` constants. /// /// Reason codes are ambiguous, and different libraries reuse the same numeric values for different errors. + /// Use [`Error::library_reason`] to compare error codes. /// /// For `ERR_LIB_SYS` the reason code is `errno`. `ERR_LIB_USER` can use any values. /// Other libraries may use [`ERR_R_*`](ffi::ERR_R_FATAL) or their own codes. From 4cb7e260a85b7157f14790444f49fcceca085447 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Thu, 25 Sep 2025 09:51:52 +0100 Subject: [PATCH 12/39] Clean-up legacy FIPS options Per BoringSSL's FIPS policy, its `main` branch is the "update branch" for FedRAMP compliance's purposes. This means that we can stop using a specific BoringSSL branch when enabling FIPS, as well as a number of hacks that allowed us to build more recent BoringSSL versions with an older pre-compiled FIPS modules. This also required slightly updating the main BoringSSL submodule, as the previous version had an issue when building with the FIPS option enabled. This is turn required some changes to the PQ patch as well as some APIs that don't seem to be exposed publicly, as well as changing some paths in the other patches. In order to allow a smooth upgrade of internal projects, the `fips-compat` feature is reduced in scope and renamed to `legacy-compat-deprecated` so that we can incrementally upgrade internal BoringSSL forks. In practice this shouldn't really be something anyone else would need, since in order to work it requires a specific mix of BoringSSL version and backported patches. --- .github/workflows/ci.yml | 16 +- .gitmodules | 3 - boring-sys/Cargo.toml | 41 +- boring-sys/build/config.rs | 24 +- boring-sys/build/main.rs | 120 +- boring-sys/deps/boringssl | 2 +- boring-sys/deps/boringssl-fips | 1 - boring-sys/patches/boring-pq.patch | 3178 ++++++++--------- boring-sys/patches/rpk.patch | 102 +- boring-sys/patches/underscore-wildcards.patch | 45 +- boring/Cargo.toml | 24 +- boring/src/bio.rs | 4 +- boring/src/fips.rs | 12 +- boring/src/lib.rs | 1 - boring/src/ssl/mod.rs | 115 +- boring/src/ssl/test/mod.rs | 4 - boring/src/x509/mod.rs | 4 +- boring/src/x509/tests/trusted_first.rs | 4 +- hyper-boring/Cargo.toml | 15 +- tokio-boring/Cargo.toml | 13 - 20 files changed, 1572 insertions(+), 2156 deletions(-) delete mode 160000 boring-sys/deps/boringssl-fips diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 110d8ba7..8ee8b46c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -202,6 +202,10 @@ jobs: run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} shell: bash - run: rustup target add ${{ matrix.target }} + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: '>=1.22.0' - name: Install target-specific APT dependencies if: "matrix.apt_packages != ''" run: sudo apt update && sudo apt install -y ${{ matrix.apt_packages }} @@ -255,18 +259,10 @@ jobs: - name: Install Rust (rustup) run: rustup update stable --no-self-update && rustup default stable shell: bash - - name: Install Clang-12 - uses: KyleMayes/install-llvm-action@v1 - with: - version: "12.0.0" - directory: ${{ runner.temp }}/llvm - name: Install golang uses: actions/setup-go@v5 with: go-version: '>=1.22.0' - - name: Add clang++-12 link - working-directory: ${{ runner.temp }}/llvm/bin - run: ln -s clang clang++-12 - name: Run tests run: cargo test --features fips - name: Test boring-sys cargo publish (FIPS) @@ -296,6 +292,10 @@ jobs: - name: Install Rust (rustup) run: rustup update stable --no-self-update && rustup default stable && rustup target add ${{ matrix.target }} shell: bash + - name: Install golang + uses: actions/setup-go@v5 + with: + go-version: '>=1.22.0' - name: Install ${{ matrix.target }} toolchain run: brew tap messense/macos-cross-toolchains && brew install ${{ matrix.target }} - name: Set BORING_BSSL_SYSROOT diff --git a/.gitmodules b/.gitmodules index 93bfb089..c1e5a527 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,3 @@ path = boring-sys/deps/boringssl url = https://github.com/google/boringssl.git ignore = dirty -[submodule "boring-sys/deps/boringssl-fips"] - path = boring-sys/deps/boringssl-fips - url = https://github.com/google/boringssl.git diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 166d62f5..86ff731c 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -19,35 +19,22 @@ include = [ "/*.toml", "/LICENSE-MIT", "/cmake/*.cmake", - # boringssl (non-FIPS) - "/deps/boringssl/src/util/32-bit-toolchain.cmake", "/deps/boringssl/**/*.[chS]", "/deps/boringssl/**/*.asm", - "/deps/boringssl/sources.json", - "/deps/boringssl/src/crypto/obj/obj_mac.num", - "/deps/boringssl/src/crypto/obj/objects.txt", + "/deps/boringssl/**/*.pl", + "/deps/boringssl/**/*.go", + "/deps/boringssl/**/*.cmake", + "/deps/boringssl/**/go.mod", + "/deps/boringssl/**/go.sum", + "/deps/boringssl/crypto/obj/obj_mac.num", + "/deps/boringssl/crypto/obj/objects.txt", + "/deps/boringssl/crypto/err/*.errordata", "/deps/boringssl/**/*.bzl", - "/deps/boringssl/src/**/*.cc", + "/deps/boringssl/**/*.cc", "/deps/boringssl/**/CMakeLists.txt", "/deps/boringssl/**/sources.cmake", + "/deps/boringssl/**/util/go_tests.txt", "/deps/boringssl/LICENSE", - # boringssl (FIPS) - "/deps/boringssl-fips/src/util/32-bit-toolchain.cmake", - "/deps/boringssl-fips/**/*.[chS]", - "/deps/boringssl-fips/**/*.asm", - "/deps/boringssl-fips/**/*.pl", - "/deps/boringssl-fips/**/*.go", - "/deps/boringssl-fips/**/go.mod", - "/deps/boringssl-fips/**/go.sum", - "/deps/boringssl-fips/sources.json", - "/deps/boringssl-fips/crypto/obj/obj_mac.num", - "/deps/boringssl-fips/crypto/obj/objects.txt", - "/deps/boringssl-fips/crypto/err/*.errordata", - "/deps/boringssl-fips/**/*.bzl", - "/deps/boringssl-fips/**/*.cc", - "/deps/boringssl-fips/**/CMakeLists.txt", - "/deps/boringssl-fips/**/sources.cmake", - "/deps/boringssl-fips/LICENSE", "/build/*", "/src", "/patches", @@ -66,14 +53,6 @@ rustdoc-args = ["--cfg", "docsrs"] # for instructions and more details on the boringssl FIPS flag. fips = [] -# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with -# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this -# feature, or else the build will fail. -fips-precompiled = [] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = [] - # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = [] diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index 5d93eda4..f586b968 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -16,8 +16,6 @@ pub(crate) struct Config { pub(crate) struct Features { pub(crate) fips: bool, - pub(crate) fips_precompiled: bool, - pub(crate) fips_link_precompiled: bool, pub(crate) pq_experimental: bool, pub(crate) rpk: bool, pub(crate) underscore_wildcards: bool, @@ -27,7 +25,6 @@ pub(crate) struct Env { pub(crate) path: Option, pub(crate) include_path: Option, pub(crate) source_path: Option, - pub(crate) precompiled_bcm_o: Option, pub(crate) assume_patched: bool, pub(crate) sysroot: Option, pub(crate) compiler_external_toolchain: Option, @@ -81,10 +78,6 @@ impl Config { panic!("`fips` and `rpk` features are mutually exclusive"); } - if self.features.fips_precompiled && self.features.rpk { - panic!("`fips-precompiled` and `rpk` features are mutually exclusive"); - } - let is_precompiled_native_lib = self.env.path.is_some(); let is_external_native_lib_source = !is_precompiled_native_lib && self.env.source_path.is_none(); @@ -107,32 +100,18 @@ impl Config { "cargo:warning=precompiled BoringSSL was provided, so patches will be ignored" ); } - - // todo(rmehra): should this even be a restriction? why not let people link a custom bcm.o? - // precompiled boringssl will include libcrypto.a - if is_precompiled_native_lib && self.features.fips_link_precompiled { - panic!("precompiled BoringSSL was provided, so FIPS configuration can't be applied"); - } - - if !is_precompiled_native_lib && self.features.fips_precompiled { - panic!("`fips-precompiled` feature requires `BORING_BSSL_FIPS_PATH` to be set"); - } } } impl Features { fn from_env() -> Self { let fips = env::var_os("CARGO_FEATURE_FIPS").is_some(); - let fips_precompiled = env::var_os("CARGO_FEATURE_FIPS_PRECOMPILED").is_some(); - let fips_link_precompiled = env::var_os("CARGO_FEATURE_FIPS_LINK_PRECOMPILED").is_some(); let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some(); let rpk = env::var_os("CARGO_FEATURE_RPK").is_some(); let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some(); Self { fips, - fips_precompiled, - fips_link_precompiled, pq_experimental, rpk, underscore_wildcards, @@ -140,7 +119,7 @@ impl Features { } pub(crate) fn is_fips_like(&self) -> bool { - self.fips || self.fips_precompiled || self.fips_link_precompiled + self.fips } } @@ -175,7 +154,6 @@ impl Env { path: boringssl_var("BORING_BSSL_PATH").map(PathBuf::from), include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH").map(PathBuf::from), source_path: boringssl_var("BORING_BSSL_SOURCE_PATH").map(PathBuf::from), - precompiled_bcm_o: boringssl_var("BORING_BSSL_PRECOMPILED_BCM_O").map(PathBuf::from), assume_patched: boringssl_var("BORING_BSSL_ASSUME_PATCHED") .is_some_and(|v| !v.is_empty()), sysroot: boringssl_var("BORING_BSSL_SYSROOT").map(PathBuf::from), diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index be9a3fa1..c95f1cd4 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -50,6 +50,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "arm64"), ("CMAKE_OSX_SYSROOT", "iphoneos"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), ( @@ -57,6 +58,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "arm64"), ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), ( @@ -64,6 +66,7 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ &[ ("CMAKE_OSX_ARCHITECTURES", "x86_64"), ("CMAKE_OSX_SYSROOT", "iphonesimulator"), + ("CMAKE_MACOSX_BUNDLE", "OFF"), ], ), // macOS @@ -114,11 +117,7 @@ fn get_boringssl_source_path(config: &Config) -> &PathBuf { static SOURCE_PATH: OnceLock = OnceLock::new(); SOURCE_PATH.get_or_init(|| { - let submodule_dir = if config.features.fips { - "boringssl-fips" - } else { - "boringssl" - }; + let submodule_dir = "boringssl"; let src_path = config.out_dir.join(submodule_dir); @@ -304,7 +303,7 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { config .manifest_dir .join(src_path) - .join("src/util/32-bit-toolchain.cmake") + .join("util/32-bit-toolchain.cmake") .as_os_str(), ); } @@ -340,55 +339,6 @@ fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { boringssl_cmake } -/// Verify that the toolchains match -/// See "Installation Instructions" under section 12.1. -// TODO: maybe this should also verify the Go and Ninja versions? But those haven't been an issue in practice ... -fn verify_fips_clang_version() -> (&'static str, &'static str) { - fn version(tool: &str) -> Option { - let output = match Command::new(tool).arg("--version").output() { - Ok(o) => o, - Err(e) => { - println!("cargo:warning=missing {tool}, trying other compilers: {e}"); - // NOTE: hard-codes that the loop below checks the version - return None; - } - }; - if !output.status.success() { - return Some(String::new()); - } - let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output"); - Some(output.lines().next().expect("empty output").to_string()) - } - - const REQUIRED_CLANG_VERSION: &str = "12.0.0"; - for (cc, cxx) in [ - ("clang-12", "clang++-12"), - ("clang", "clang++"), - ("cc", "c++"), - ] { - let (Some(cc_version), Some(cxx_version)) = (version(cc), version(cxx)) else { - continue; - }; - - if cc_version.contains(REQUIRED_CLANG_VERSION) { - assert!( - cxx_version.contains(REQUIRED_CLANG_VERSION), - "mismatched versions of cc and c++" - ); - return (cc, cxx); - } else if cc == "cc" { - panic!( - "unsupported clang version \"{cc_version}\": FIPS requires clang {REQUIRED_CLANG_VERSION}" - ); - } else if !cc_version.is_empty() { - println!( - "cargo:warning=FIPS requires clang version {REQUIRED_CLANG_VERSION}, skipping incompatible version \"{cc_version}\"" - ); - } - } - unreachable!() -} - fn pick_best_android_ndk_toolchain(toolchains_dir: &Path) -> std::io::Result { let toolchains = std::fs::read_dir(toolchains_dir)?.collect::, _>>()?; // First look for one of the toolchains that Google has documented. @@ -591,66 +541,17 @@ fn built_boring_source_path(config: &Config) -> &PathBuf { } if config.features.fips { - let (clang, clangxx) = verify_fips_clang_version(); - cfg.define("CMAKE_C_COMPILER", clang) - .define("CMAKE_CXX_COMPILER", clangxx) - .define("CMAKE_ASM_COMPILER", clang) + cfg.define("CMAKE_C_COMPILER", "clang") + .define("CMAKE_CXX_COMPILER", "clang++") + .define("CMAKE_ASM_COMPILER", "clang") .define("FIPS", "1"); } - if config.features.fips_link_precompiled { - cfg.define("FIPS", "1"); - } - cfg.build_target("ssl").build(); cfg.build_target("crypto").build() }) } -fn link_in_precompiled_bcm_o(config: &Config) { - println!("cargo:warning=linking in precompiled `bcm.o` module"); - - let bssl_dir = built_boring_source_path(config); - let bcm_o_src_path = config.env.precompiled_bcm_o.as_ref() - .expect("`fips-link-precompiled` requires `BORING_BSSL_FIPS_PRECOMPILED_BCM_O` env variable to be specified"); - - let libcrypto_path = bssl_dir - .join("build/crypto/libcrypto.a") - .canonicalize() - .unwrap(); - - let bcm_o_dst_path = bssl_dir.join("build/bcm-fips.o"); - - fs::copy(bcm_o_src_path, &bcm_o_dst_path).unwrap(); - - // check that fips module is named as expected - let out = run_command( - Command::new("ar") - .arg("t") - .arg(&libcrypto_path) - .arg("bcm.o"), - ) - .unwrap(); - - assert_eq!( - String::from_utf8(out.stdout).unwrap().trim(), - "bcm.o", - "failed to verify FIPS module name" - ); - - // insert fips bcm.o before bcm.o into libcrypto.a, - // so for all duplicate symbols the older fips bcm.o is used - // (this causes the need for extra linker flags to deal with duplicate symbols) - // (as long as the newer module does not define new symbols, one may also remove it, - // but once there are new symbols it would cause missing symbols at linking stage) - run_command( - Command::new("ar") - .args(["rb", "bcm.o"]) - .args([&libcrypto_path, &bcm_o_dst_path]), - ) - .unwrap(); -} - fn get_cpp_runtime_lib(config: &Config) -> Option { if let Some(ref cpp_lib) = config.env.cpp_runtime_lib { return cpp_lib.clone().into_string().ok(); @@ -709,10 +610,6 @@ fn emit_link_directives(config: &Config) { ); } - if config.features.fips_link_precompiled { - link_in_precompiled_bcm_o(config); - } - if let Some(cpp_lib) = get_cpp_runtime_lib(config) { println!("cargo:rustc-link-lib={cpp_lib}"); } @@ -785,7 +682,6 @@ fn generate_bindings(config: &Config) { "des.h", "dtls1.h", "hkdf.h", - #[cfg(not(feature = "fips"))] "hpke.h", "hmac.h", "hrss.h", diff --git a/boring-sys/deps/boringssl b/boring-sys/deps/boringssl index 44b3df6f..478b28ab 160000 --- a/boring-sys/deps/boringssl +++ b/boring-sys/deps/boringssl @@ -1 +1 @@ -Subproject commit 44b3df6f03d85c901767250329c571db405122d5 +Subproject commit 478b28ab12f2001a03261624261fd041f5439706 diff --git a/boring-sys/deps/boringssl-fips b/boring-sys/deps/boringssl-fips deleted file mode 160000 index 853ca1ea..00000000 --- a/boring-sys/deps/boringssl-fips +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 853ca1ea1168dff08011e5d42d94609cc0ca2e27 diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 38488004..fb5df8b9 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -66,141 +66,26 @@ Cf RTG-2076 RTG-2051 RTG-2508 RTG-2707 RTG-2607 RTG-3239 delete mode 100644 src/crypto/kyber/kyber_test.cc delete mode 100644 src/crypto/kyber/kyber_tests.txt -diff --git a/BUILD.generated.bzl b/BUILD.generated.bzl -index 738e1055f..9466757a2 100644 ---- a/BUILD.generated.bzl -+++ b/BUILD.generated.bzl -@@ -253,7 +253,6 @@ crypto_internal_headers = [ - "src/crypto/fipsmodule/tls/internal.h", - "src/crypto/hrss/internal.h", - "src/crypto/internal.h", -- "src/crypto/kyber/internal.h", - "src/crypto/lhash/internal.h", - "src/crypto/obj/obj_dat.h", - "src/crypto/pkcs7/internal.h", -@@ -382,8 +381,8 @@ crypto_sources = [ - "src/crypto/fipsmodule/fips_shared_support.c", - "src/crypto/hpke/hpke.c", - "src/crypto/hrss/hrss.c", -- "src/crypto/kyber/keccak.c", -- "src/crypto/kyber/kyber.c", -+ "src/crypto/kyber/kyber512.c", -+ "src/crypto/kyber/kyber768.c", - "src/crypto/lhash/lhash.c", - "src/crypto/mem.c", - "src/crypto/obj/obj.c", -diff --git a/BUILD.generated_tests.bzl b/BUILD.generated_tests.bzl -index 92dec1e01..8f70dedc0 100644 ---- a/BUILD.generated_tests.bzl -+++ b/BUILD.generated_tests.bzl -@@ -40,7 +40,6 @@ test_support_sources = [ - "src/crypto/fipsmodule/tls/internal.h", - "src/crypto/hrss/internal.h", - "src/crypto/internal.h", -- "src/crypto/kyber/internal.h", - "src/crypto/lhash/internal.h", - "src/crypto/obj/obj_dat.h", - "src/crypto/pkcs7/internal.h", -@@ -124,7 +123,6 @@ crypto_test_sources = [ - "src/crypto/hpke/hpke_test.cc", - "src/crypto/hrss/hrss_test.cc", - "src/crypto/impl_dispatch_test.cc", -- "src/crypto/kyber/kyber_test.cc", - "src/crypto/lhash/lhash_test.cc", - "src/crypto/obj/obj_test.cc", - "src/crypto/pem/pem_test.cc", -@@ -218,8 +216,6 @@ crypto_test_data = [ - "src/crypto/fipsmodule/rand/ctrdrbg_vectors.txt", - "src/crypto/hmac_extra/hmac_tests.txt", - "src/crypto/hpke/hpke_test_vectors.txt", -- "src/crypto/kyber/keccak_tests.txt", -- "src/crypto/kyber/kyber_tests.txt", - "src/crypto/pkcs8/test/empty_password.p12", - "src/crypto/pkcs8/test/no_encryption.p12", - "src/crypto/pkcs8/test/nss.p12", -diff --git a/CMakeLists.txt b/CMakeLists.txt -index faed2befa..931c0e3a8 100644 ---- a/CMakeLists.txt -+++ b/CMakeLists.txt -@@ -375,8 +375,8 @@ add_library( - src/crypto/fipsmodule/fips_shared_support.c - src/crypto/hpke/hpke.c - src/crypto/hrss/hrss.c -- src/crypto/kyber/keccak.c -- src/crypto/kyber/kyber.c -+ src/crypto/kyber/kyber512.c -+ src/crypto/kyber/kyber768.c - src/crypto/lhash/lhash.c - src/crypto/mem.c - src/crypto/obj/obj.c -diff --git a/sources.json b/sources.json -index 4c0048e1d..f6ea5c40f 100644 ---- a/sources.json -+++ b/sources.json -@@ -111,8 +111,8 @@ - "src/crypto/fipsmodule/fips_shared_support.c", - "src/crypto/hpke/hpke.c", - "src/crypto/hrss/hrss.c", -- "src/crypto/kyber/keccak.c", -- "src/crypto/kyber/kyber.c", -+ "src/crypto/kyber/kyber512.c", -+ "src/crypto/kyber/kyber768.c", - "src/crypto/lhash/lhash.c", - "src/crypto/mem.c", - "src/crypto/obj/obj.c", -@@ -549,7 +549,6 @@ - "src/crypto/hpke/hpke_test.cc", - "src/crypto/hrss/hrss_test.cc", - "src/crypto/impl_dispatch_test.cc", -- "src/crypto/kyber/kyber_test.cc", - "src/crypto/lhash/lhash_test.cc", - "src/crypto/obj/obj_test.cc", - "src/crypto/pem/pem_test.cc", -@@ -634,8 +633,6 @@ - "src/crypto/fipsmodule/rand/ctrdrbg_vectors.txt", - "src/crypto/hmac_extra/hmac_tests.txt", - "src/crypto/hpke/hpke_test_vectors.txt", -- "src/crypto/kyber/keccak_tests.txt", -- "src/crypto/kyber/kyber_tests.txt", - "src/crypto/pkcs8/test/empty_password.p12", - "src/crypto/pkcs8/test/no_encryption.p12", - "src/crypto/pkcs8/test/nss.p12", -@@ -1060,4 +1057,4 @@ - "urandom_test": [ - "src/crypto/fipsmodule/rand/urandom_test.cc" - ] --} -\ No newline at end of file -+} -diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt -index cdb5ddca1..2052fa791 100644 ---- a/src/crypto/CMakeLists.txt -+++ b/src/crypto/CMakeLists.txt -@@ -170,8 +170,8 @@ add_library( - ex_data.c +diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt +index a594b9e9d..ed468237f 100644 +--- a/crypto/CMakeLists.txt ++++ b/crypto/CMakeLists.txt +@@ -176,7 +176,8 @@ add_library( hpke/hpke.c hrss/hrss.c -- kyber/keccak.c + keccak/keccak.c - kyber/kyber.c + kyber/kyber512.c + kyber/kyber768.c lhash/lhash.c mem.c obj/obj.c -@@ -400,7 +400,6 @@ add_executable( - hmac_extra/hmac_test.cc - hrss/hrss_test.cc - impl_dispatch_test.cc -- kyber/kyber_test.cc - lhash/lhash_test.cc - obj/obj_test.cc - pem/pem_test.cc -diff --git a/src/crypto/kyber/internal.h b/src/crypto/kyber/internal.h +diff --git a/crypto/kyber/internal.h b/crypto/kyber/internal.h deleted file mode 100644 -index b3bfa86b8..000000000 ---- a/src/crypto/kyber/internal.h +index b11211726..000000000 +--- a/crypto/kyber/internal.h +++ /dev/null -@@ -1,91 +0,0 @@ +@@ -1,60 +0,0 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any @@ -235,37 +120,6 @@ index b3bfa86b8..000000000 -// necessary to generate a key. -#define KYBER_GENERATE_KEY_ENTROPY 64 - --struct BORINGSSL_keccak_st { -- uint64_t state[25]; -- size_t rate_bytes; -- size_t offset; --}; -- --enum boringssl_keccak_config_t { -- boringssl_sha3_256, -- boringssl_sha3_512, -- boringssl_shake128, -- boringssl_shake256, --}; -- --// BORINGSSL_keccak hashes |in_len| bytes from |in| and writes |out_len| bytes --// of output to |out|. If the |config| specifies a fixed-output function, like --// SHA3-256, then |out_len| must be the correct length for that function. --OPENSSL_EXPORT void BORINGSSL_keccak(uint8_t *out, size_t out_len, -- const uint8_t *in, size_t in_len, -- enum boringssl_keccak_config_t config); -- --// BORINGSSL_keccak_init absorbs |in_len| bytes from |in| and sets up |ctx| for --// squeezing. The |config| must specify a SHAKE variant, otherwise callers --// should use |BORINGSSL_keccak|. --OPENSSL_EXPORT void BORINGSSL_keccak_init( -- struct BORINGSSL_keccak_st *ctx, const uint8_t *in, size_t in_len, -- enum boringssl_keccak_config_t config); -- --// BORINGSSL_keccak_squeeze writes |out_len| bytes to |out| from |ctx|. --OPENSSL_EXPORT void BORINGSSL_keccak_squeeze(struct BORINGSSL_keccak_st *ctx, -- uint8_t *out, size_t out_len); -- -// KYBER_generate_key_external_entropy is a deterministic function to create a -// pair of Kyber768 keys, using the supplied entropy. The entropy needs to be -// uniformly random generated. This function is should only be used for tests, @@ -292,221 +146,11 @@ index b3bfa86b8..000000000 -#endif - -#endif // OPENSSL_HEADER_CRYPTO_KYBER_INTERNAL_H -diff --git a/src/crypto/kyber/keccak.c b/src/crypto/kyber/keccak.c -deleted file mode 100644 -index f1c012d11..000000000 ---- a/src/crypto/kyber/keccak.c -+++ /dev/null -@@ -1,204 +0,0 @@ --/* Copyright (c) 2023, Google Inc. -- * -- * Permission to use, copy, modify, and/or distribute this software for any -- * purpose with or without fee is hereby granted, provided that the above -- * copyright notice and this permission notice appear in all copies. -- * -- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -- * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION -- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN -- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -- --#include -- --#include --#include -- --#include "../internal.h" --#include "./internal.h" -- -- --// keccak_f implements the Keccak-1600 permutation as described at --// https://keccak.team/keccak_specs_summary.html. Each lane is represented as a --// 64-bit value and the 5×5 lanes are stored as an array in row-major order. --static void keccak_f(uint64_t state[25]) { -- static const int kNumRounds = 24; -- for (int round = 0; round < kNumRounds; round++) { -- // θ step -- uint64_t c[5]; -- for (int x = 0; x < 5; x++) { -- c[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ -- state[x + 20]; -- } -- -- for (int x = 0; x < 5; x++) { -- const uint64_t d = c[(x + 4) % 5] ^ CRYPTO_rotl_u64(c[(x + 1) % 5], 1); -- for (int y = 0; y < 5; y++) { -- state[y * 5 + x] ^= d; -- } -- } -- -- // ρ and π steps. -- // -- // These steps involve a mapping of the state matrix. Each input point, -- // (x,y), is rotated and written to the point (y, 2x + 3y). In the Keccak -- // pseudo-code a separate array is used because an in-place operation would -- // overwrite some values that are subsequently needed. However, the mapping -- // forms a trail through 24 of the 25 values so we can do it in place with -- // only a single temporary variable. -- // -- // Start with (1, 0). The value here will be mapped and end up at (0, 2). -- // That value will end up at (2, 1), then (1, 2), and so on. After 24 -- // steps, 24 of the 25 values have been hit (as this mapping is injective) -- // and the sequence will repeat. All that remains is to handle the element -- // at (0, 0), but the rotation for that element is zero, and it goes to (0, -- // 0), so we can ignore it. -- static const uint8_t kIndexes[24] = {10, 7, 11, 17, 18, 3, 5, 16, -- 8, 21, 24, 4, 15, 23, 19, 13, -- 12, 2, 20, 14, 22, 9, 6, 1}; -- static const uint8_t kRotations[24] = {1, 3, 6, 10, 15, 21, 28, 36, -- 45, 55, 2, 14, 27, 41, 56, 8, -- 25, 43, 62, 18, 39, 61, 20, 44}; -- uint64_t prev_value = state[1]; -- for (int i = 0; i < 24; i++) { -- const uint64_t value = CRYPTO_rotl_u64(prev_value, kRotations[i]); -- const size_t index = kIndexes[i]; -- prev_value = state[index]; -- state[index] = value; -- } -- -- // χ step -- for (int y = 0; y < 5; y++) { -- const int row_index = 5 * y; -- const uint64_t orig_x0 = state[row_index]; -- const uint64_t orig_x1 = state[row_index + 1]; -- state[row_index] ^= ~orig_x1 & state[row_index + 2]; -- state[row_index + 1] ^= ~state[row_index + 2] & state[row_index + 3]; -- state[row_index + 2] ^= ~state[row_index + 3] & state[row_index + 4]; -- state[row_index + 3] ^= ~state[row_index + 4] & orig_x0; -- state[row_index + 4] ^= ~orig_x0 & orig_x1; -- } -- -- // ι step -- // -- // From https://keccak.team/files/Keccak-reference-3.0.pdf, section -- // 1.2, the round constants are based on the output of a LFSR. Thus, as -- // suggested in the appendix of of -- // https://keccak.team/keccak_specs_summary.html, the values are -- // simply encoded here. -- static const uint64_t kRoundConstants[24] = { -- 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, -- 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, -- 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, -- 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, -- 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, -- 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, -- 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, -- 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, -- }; -- -- state[0] ^= kRoundConstants[round]; -- } --} -- --static void keccak_init(struct BORINGSSL_keccak_st *ctx, -- size_t *out_required_out_len, const uint8_t *in, -- size_t in_len, enum boringssl_keccak_config_t config) { -- size_t capacity_bytes; -- uint8_t terminator; -- switch (config) { -- case boringssl_sha3_256: -- capacity_bytes = 512 / 8; -- *out_required_out_len = 32; -- terminator = 0x06; -- break; -- case boringssl_sha3_512: -- capacity_bytes = 1024 / 8; -- *out_required_out_len = 64; -- terminator = 0x06; -- break; -- case boringssl_shake128: -- capacity_bytes = 256 / 8; -- *out_required_out_len = 0; -- terminator = 0x1f; -- break; -- case boringssl_shake256: -- capacity_bytes = 512 / 8; -- *out_required_out_len = 0; -- terminator = 0x1f; -- break; -- default: -- abort(); -- } -- -- OPENSSL_memset(ctx, 0, sizeof(*ctx)); -- ctx->rate_bytes = 200 - capacity_bytes; -- assert(ctx->rate_bytes % 8 == 0); -- const size_t rate_words = ctx->rate_bytes / 8; -- -- while (in_len >= ctx->rate_bytes) { -- for (size_t i = 0; i < rate_words; i++) { -- ctx->state[i] ^= CRYPTO_load_u64_le(in + 8 * i); -- } -- keccak_f(ctx->state); -- in += ctx->rate_bytes; -- in_len -= ctx->rate_bytes; -- } -- -- // XOR the final block. Accessing |ctx->state| as a |uint8_t*| is allowed by -- // strict aliasing because we require |uint8_t| to be a character type. -- uint8_t *state_bytes = (uint8_t *)ctx->state; -- assert(in_len < ctx->rate_bytes); -- for (size_t i = 0; i < in_len; i++) { -- state_bytes[i] ^= in[i]; -- } -- state_bytes[in_len] ^= terminator; -- state_bytes[ctx->rate_bytes - 1] ^= 0x80; -- keccak_f(ctx->state); --} -- --void BORINGSSL_keccak(uint8_t *out, size_t out_len, const uint8_t *in, -- size_t in_len, enum boringssl_keccak_config_t config) { -- struct BORINGSSL_keccak_st ctx; -- size_t required_out_len; -- keccak_init(&ctx, &required_out_len, in, in_len, config); -- if (required_out_len != 0 && out_len != required_out_len) { -- abort(); -- } -- BORINGSSL_keccak_squeeze(&ctx, out, out_len); --} -- --void BORINGSSL_keccak_init(struct BORINGSSL_keccak_st *ctx, const uint8_t *in, -- size_t in_len, -- enum boringssl_keccak_config_t config) { -- size_t required_out_len; -- keccak_init(ctx, &required_out_len, in, in_len, config); -- if (required_out_len != 0) { -- abort(); -- } --} -- --void BORINGSSL_keccak_squeeze(struct BORINGSSL_keccak_st *ctx, uint8_t *out, -- size_t out_len) { -- // Accessing |ctx->state| as a |uint8_t*| is allowed by strict aliasing -- // because we require |uint8_t| to be a character type. -- const uint8_t *state_bytes = (const uint8_t *)ctx->state; -- while (out_len) { -- size_t remaining = ctx->rate_bytes - ctx->offset; -- size_t todo = out_len; -- if (todo > remaining) { -- todo = remaining; -- } -- OPENSSL_memcpy(out, &state_bytes[ctx->offset], todo); -- out += todo; -- out_len -= todo; -- ctx->offset += todo; -- if (ctx->offset == ctx->rate_bytes) { -- keccak_f(ctx->state); -- ctx->offset = 0; -- } -- } --} -diff --git a/src/crypto/kyber/kyber.c b/src/crypto/kyber/kyber.c -index 776c085f9..ccb5b3d9b 100644 ---- a/src/crypto/kyber/kyber.c -+++ b/src/crypto/kyber/kyber.c -@@ -1,833 +1,2426 @@ +diff --git a/crypto/kyber/kyber.c b/crypto/kyber/kyber.c +index d3ea02090..ccb5b3d9b 100644 +--- a/crypto/kyber/kyber.c ++++ b/crypto/kyber/kyber.c +@@ -1,835 +1,2426 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any @@ -547,17 +191,17 @@ index 776c085f9..ccb5b3d9b 100644 +// implementation or https://github.com/cloudflare/circl/tree/main/pke/kyber +// +// - Option to keep A stored in private key. - --#include ++ +#ifndef KYBER_K +#error "Don't compile this file direcly" +#endif --#include --#include -+#include + #include +#include +-#include +-#include +- -#include -#include +#include @@ -565,9 +209,29 @@ index 776c085f9..ccb5b3d9b 100644 +#include #include "../internal.h" +-#include "../keccak/internal.h" -#include "./internal.h" -- -- ++ ++#if (KYBER_K == 2) ++#define KYBER_NAMESPACE(s) KYBER512_##s ++#elif (KYBER_K == 3) ++#define KYBER_NAMESPACE(s) KYBER768_##s ++#elif (KYBER_K == 4) ++#define KYBER_NAMESPACE(s) KYBER1024_##s ++#else ++#error "KYBER_K must be in {2,3,4}" ++#endif ++ ++#define public_key KYBER_NAMESPACE(public_key) ++#define private_key KYBER_NAMESPACE(private_key) ++ ++#define generate_key KYBER_NAMESPACE(generate_key) ++#define encap KYBER_NAMESPACE(encap) ++#define decap KYBER_NAMESPACE(decap) ++#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) ++#define parse_public_key KYBER_NAMESPACE(parse_public_key) + + -// See -// https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf - @@ -602,9 +266,10 @@ index 776c085f9..ccb5b3d9b 100644 -} matrix; - -// This bit of Python will be referenced in some of the following comments: --// + // -// p = 3329 --// ++// params.h + // -// def bitreverse(i): -// ret = 0 -// for n in range(7): @@ -613,7 +278,9 @@ index 776c085f9..ccb5b3d9b 100644 -// ret |= bit -// i >>= 1 -// return ret -- ++#define KYBER_N 256 ++#define KYBER_Q 3329 + -// kNTTRoots = [pow(17, bitreverse(i), p) for i in range(128)] -static const uint16_t kNTTRoots[128] = { - 1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, @@ -627,110 +294,6 @@ index 776c085f9..ccb5b3d9b 100644 - 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, - 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, - 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154, --}; - --// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] --static const uint16_t kInverseNTTRoots[128] = { -- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, -- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, -- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, -- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, -- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, -- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, -- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, -- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, -- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, -- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, -- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, --}; -+#if (KYBER_K == 2) -+#define KYBER_NAMESPACE(s) KYBER512_##s -+#elif (KYBER_K == 3) -+#define KYBER_NAMESPACE(s) KYBER768_##s -+#elif (KYBER_K == 4) -+#define KYBER_NAMESPACE(s) KYBER1024_##s -+#else -+#error "KYBER_K must be in {2,3,4}" -+#endif - --// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] --static const uint16_t kModRoots[128] = { -- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, -- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, -- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, -- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, -- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, -- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, -- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, -- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, -- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, -- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, -- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, --}; -+#define public_key KYBER_NAMESPACE(public_key) -+#define private_key KYBER_NAMESPACE(private_key) - --// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. --static uint16_t reduce_once(uint16_t x) { -- assert(x < 2 * kPrime); -- const uint16_t subtracted = x - kPrime; -- uint16_t mask = 0u - (subtracted >> 15); -- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber -- // overall and Clang still produces constant-time code using `csel`. On other -- // platforms & compilers on godbolt that we care about, this code also -- // produces constant-time output. -- return (mask & x) | (~mask & subtracted); --} -- --// constant time reduce x mod kPrime using Barrett reduction. x must be less --// than kPrime + 2×kPrime². --static uint16_t reduce(uint32_t x) { -- assert(x < kPrime + 2u * kPrime * kPrime); -- uint64_t product = (uint64_t)x * kBarrettMultiplier; -- uint32_t quotient = product >> kBarrettShift; -- uint32_t remainder = x - quotient * kPrime; -- return reduce_once(remainder); --} -- --static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } -- --// In place number theoretic transform of a given scalar. --// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this --// transform leaves off the last iteration of the usual FFT code, with the 128 --// relevant roots of unity being stored in |kNTTRoots|. This means the output --// should be seen as 128 elements in GF(3329^2), with the coefficients of the --// elements being consecutive entries in |s->c|. --static void scalar_ntt(scalar *s) { -- int offset = DEGREE; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int step = 1; step < DEGREE / 2; step <<= 1) { -- offset >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- const uint32_t step_root = kNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = reduce(step_root * s->c[j + offset]); -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce_once(even - odd + kPrime); -- } -- k += 2 * offset; -+#define generate_key KYBER_NAMESPACE(generate_key) -+#define encap KYBER_NAMESPACE(encap) -+#define decap KYBER_NAMESPACE(decap) -+#define marshal_public_key KYBER_NAMESPACE(marshal_public_key) -+#define parse_public_key KYBER_NAMESPACE(parse_public_key) -+ -+ -+// -+// params.h -+// -+#define KYBER_N 256 -+#define KYBER_Q 3329 -+ +#define KYBER_SYMBYTES 32 /* size in bytes of hashes, and seeds */ +#define KYBER_SSBYTES 32 /* size in bytes of shared key */ + @@ -1112,9 +675,9 @@ index 776c085f9..ccb5b3d9b 100644 + a = (d >> (6*j+0)) & 0x7; + b = (d >> (6*j+3)) & 0x7; + r->coeffs[4*i+j] = a - b; - } - } - } ++ } ++ } ++} +#endif + +static void poly_cbd_eta1(poly *r, const uint8_t buf[KYBER_ETA1*KYBER_N/4]) @@ -1127,10 +690,7 @@ index 776c085f9..ccb5b3d9b 100644 +#error "This implementation requires eta1 in {2,3}" +#endif +} - --static void vector_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_ntt(&a->v[i]); ++ +static void poly_cbd_eta2(poly *r, const uint8_t buf[KYBER_ETA2*KYBER_N/4]) +{ +#if KYBER_ETA2 == 2 @@ -1157,8 +717,21 @@ index 776c085f9..ccb5b3d9b 100644 + 5, 69, 37, 101, 21, 85, 53, 117, 13, 77, 45, 109, 29, 93, 61, 125, + 3, 67, 35, 99, 19, 83, 51, 115, 11, 75, 43, 107, 27, 91, 59, 123, + 7, 71, 39, 103, 23, 87, 55, 119, 15, 79, 47, 111, 31, 95, 63, 127 -+}; -+ + }; + +-// kInverseNTTRoots = [pow(17, -bitreverse(i), p) for i in range(128)] +-static const uint16_t kInverseNTTRoots[128] = { +- 1, 1600, 40, 749, 2481, 1432, 2699, 687, 1583, 2760, 69, 543, +- 2532, 3136, 1410, 2267, 2508, 1355, 450, 936, 447, 2794, 1235, 1903, +- 1996, 1089, 3273, 283, 1853, 1990, 882, 3033, 2419, 2102, 219, 855, +- 2681, 1848, 712, 682, 927, 1795, 461, 1891, 2877, 2522, 1894, 1010, +- 1414, 2009, 3296, 464, 2697, 816, 1352, 2679, 1274, 1052, 1025, 2132, +- 1573, 76, 2998, 3040, 1175, 2444, 394, 1219, 2300, 1455, 2117, 1607, +- 2443, 554, 1179, 2186, 2303, 2926, 2237, 525, 735, 863, 2768, 1230, +- 2572, 556, 3010, 2266, 1684, 1239, 780, 2954, 109, 1292, 1031, 1745, +- 2688, 3061, 992, 2596, 941, 892, 1021, 2390, 642, 1868, 2377, 1482, +- 1540, 540, 1678, 1626, 279, 314, 1173, 2573, 3096, 48, 667, 1920, +- 2229, 1041, 2606, 1692, 680, 2746, 568, 3312, +void init_ntt() { + unsigned int i; + int16_t tmp[128]; @@ -1173,8 +746,8 @@ index 776c085f9..ccb5b3d9b 100644 + zetas[i] -= KYBER_Q; + if(zetas[i] < -KYBER_Q/2) + zetas[i] += KYBER_Q; - } - } ++ } ++} +*/ + +static const int16_t zetas[128] = { @@ -1194,8 +767,21 @@ index 776c085f9..ccb5b3d9b 100644 + -1215, -136, 1218, -1335, -874, 220, -1187, -1659, + -1185, -1530, -1278, 794, -1510, -854, -870, 478, + -108, -308, 996, 991, 958, -1460, 1522, 1628 -+}; -+ + }; + +-// kModRoots = [pow(17, 2*bitreverse(i) + 1, p) for i in range(128)] +-static const uint16_t kModRoots[128] = { +- 17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, +- 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, +- 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, +- 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, +- 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, +- 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, +- 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, +- 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, +- 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, +- 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, +- 2110, 1219, 2935, 394, 885, 2444, 2154, 1175, +/************************************************* +* Name: fqmul +* @@ -1209,26 +795,7 @@ index 776c085f9..ccb5b3d9b 100644 +static int16_t fqmul(int16_t a, int16_t b) { + return montgomery_reduce((int32_t)a*b); +} - --// In place inverse number theoretic transform of a given scalar, with pairs of --// entries of s->v being interpreted as elements of GF(3329^2). Just as with the --// number theoretic transform, this leaves off the first step of the normal iFFT --// to account for the fact that 3329 does not have a 512th root of unity, using --// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. --static void scalar_inverse_ntt(scalar *s) { -- int step = DEGREE / 2; -- // `int` is used here because using `size_t` throughout caused a ~5% slowdown -- // with Clang 14 on Aarch64. -- for (int offset = 2; offset < DEGREE; offset <<= 1) { -- step >>= 1; -- int k = 0; -- for (int i = 0; i < step; i++) { -- uint32_t step_root = kInverseNTTRoots[i + step]; -- for (int j = k; j < k + offset; j++) { -- uint16_t odd = s->c[j + offset]; -- uint16_t even = s->c[j]; -- s->c[j] = reduce_once(odd + even); -- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); ++ +/************************************************* +* Name: ntt +* @@ -1249,18 +816,11 @@ index 776c085f9..ccb5b3d9b 100644 + t = fqmul(zeta, r[j + len]); + r[j + len] = r[j] - t; + r[j] = r[j] + t; - } -- k += 2 * offset; - } - } -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = reduce(s->c[i] * kInverseDegree); -- } - } - --static void vector_inverse_ntt(vector *a) { -- for (int i = 0; i < RANK; i++) { -- scalar_inverse_ntt(&a->v[i]); ++ } ++ } ++ } ++} ++ +/************************************************* +* Name: invntt_tomont +* @@ -1286,7 +846,7 @@ index 776c085f9..ccb5b3d9b 100644 + r[j + len] = fqmul(zeta, r[j + len]); + } + } - } ++ } + + for(j = 0; j < 256; j++) + r[j] = fqmul(r[j], f); @@ -1310,11 +870,8 @@ index 776c085f9..ccb5b3d9b 100644 + r[0] += fqmul(a[0], b[0]); + r[1] = fqmul(a[0], b[1]); + r[1] += fqmul(a[1], b[0]); - } - --static void scalar_add(scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE; i++) { -- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); ++} ++ +// +// poly.c +// @@ -1353,7 +910,7 @@ index 776c085f9..ccb5b3d9b 100644 + r[2] = t[4] | (t[5] << 4); + r[3] = t[6] | (t[7] << 4); + r += 4; - } ++ } +#elif (KYBER_POLYCOMPRESSEDBYTES == 160) + for(i=0;ic[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); ++} ++ +/************************************************* +* Name: poly_decompress +* @@ -1418,29 +972,12 @@ index 776c085f9..ccb5b3d9b 100644 + + for(j=0;j<8;j++) + r->coeffs[8*i+j] = ((uint32_t)(t[j] & 31)*KYBER_Q + 16) >> 5; - } ++ } +#else +#error "KYBER_POLYCOMPRESSEDBYTES needs to be in {128, 160}" +#endif - } - --// Multiplying two scalars in the number theoretically transformed state. Since --// 3329 does not have a 512th root of unity, this means we have to interpret --// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 --// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is --// stored in the precomputed |kModRoots| table. Note that our Barrett transform --// only allows us to multipy two reduced numbers together, so we need some --// intermediate reduction steps, even if an uint64_t could hold 3 multiplied --// numbers. --static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { -- for (int i = 0; i < DEGREE / 2; i++) { -- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; -- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; -- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; -- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; -- out->c[2 * i] = -- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); -- out->c[2 * i + 1] = reduce(img_real + real_img); ++} ++ +/************************************************* +* Name: poly_tobytes +* @@ -1464,12 +1001,9 @@ index 776c085f9..ccb5b3d9b 100644 + r[3*i+0] = (t0 >> 0); + r[3*i+1] = (t0 >> 8) | (t1 << 4); + r[3*i+2] = (t1 >> 4); - } - } - --static void vector_add(vector *lhs, const vector *rhs) { -- for (int i = 0; i < RANK; i++) { -- scalar_add(&lhs->v[i], &rhs->v[i]); ++ } ++} ++ +/************************************************* +* Name: poly_frombytes +* @@ -1486,16 +1020,9 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;icoeffs[2*i] = ((a[3*i+0] >> 0) | ((uint16_t)a[3*i+1] << 8)) & 0xFFF; + r->coeffs[2*i+1] = ((a[3*i+1] >> 4) | ((uint16_t)a[3*i+2] << 4)) & 0xFFF; - } - } - --static void matrix_mult(vector *out, const matrix *m, const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[i][j], &a->v[j]); -- scalar_add(&out->v[i], &product); ++ } ++} ++ +/************************************************* +* Name: poly_frommsg +* @@ -1517,18 +1044,10 @@ index 776c085f9..ccb5b3d9b 100644 + for(j=0;j<8;j++) { + mask = -(int16_t)value_barrier_u32((msg[i] >> j)&1); + r->coeffs[8*i+j] = mask & ((KYBER_Q+1)/2); - } - } - } - --static void matrix_mult_transpose(vector *out, const matrix *m, -- const vector *a) { -- vector_zero(out); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- scalar product; -- scalar_mult(&product, &m->v[j][i], &a->v[j]); -- scalar_add(&out->v[i], &product); ++ } ++ } ++} ++ +/************************************************* +* Name: poly_tomsg +* @@ -1552,18 +1071,10 @@ index 776c085f9..ccb5b3d9b 100644 + t >>= 28; + t &= 1; + msg[i] |= t << j; - } - } - } - --static void scalar_inner_product(scalar *out, const vector *lhs, -- const vector *rhs) { -- scalar_zero(out); -- for (int i = 0; i < RANK; i++) { -- scalar product; -- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); -- scalar_add(out, &product); -- } ++ } ++ } ++} ++ +/************************************************* +* Name: poly_getnoise_eta1 +* @@ -1581,32 +1092,8 @@ index 776c085f9..ccb5b3d9b 100644 + uint8_t buf[KYBER_ETA1*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta1(r, buf); - } - --// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get --// uniformly distributed elements. This is used for matrix expansion and only --// operates on public inputs. --static void scalar_from_keccak_vartime(scalar *out, -- struct BORINGSSL_keccak_st *keccak_ctx) { -- assert(keccak_ctx->offset == 0); -- assert(keccak_ctx->rate_bytes == 168); -- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); -- -- int done = 0; -- while (done < DEGREE) { -- uint8_t block[168]; -- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); -- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { -- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); -- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; -- if (d1 < kPrime) { -- out->c[done++] = d1; -- } -- if (d2 < kPrime && done < DEGREE) { -- out->c[done++] = d2; -- } -- } -- } ++} ++ +/************************************************* +* Name: poly_getnoise_eta2 +* @@ -1624,34 +1111,8 @@ index 776c085f9..ccb5b3d9b 100644 + uint8_t buf[KYBER_ETA2*KYBER_N/4]; + prf(buf, sizeof(buf), seed, nonce); + poly_cbd_eta2(r, buf); - } - --// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call --// included. Creates binominally distributed elements by sampling 2*|eta| bits, --// and setting the coefficient to the count of the first bits minus the count of --// the second bits, resulting in a centered binomial distribution. Since eta is --// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, --// and 0 with probability 3/8. --static void scalar_centered_binomial_distribution_eta_2_with_prf( -- scalar *out, const uint8_t input[33]) { -- uint8_t entropy[128]; -- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); -- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); -- -- for (int i = 0; i < DEGREE; i += 2) { -- uint8_t byte = entropy[i / 2]; -- -- uint16_t value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i] = reduce_once(value); -- -- byte >>= 4; -- value = kPrime; -- value += (byte & 1) + ((byte >> 1) & 1); -- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); -- out->c[i + 1] = reduce_once(value); -- } ++} ++ + +/************************************************* +* Name: poly_ntt @@ -1666,19 +1127,8 @@ index 776c085f9..ccb5b3d9b 100644 +{ + ntt(r->coeffs); + poly_reduce(r); - } - --// Generates a secret vector by using --// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed --// appending and incrementing |counter| for entry of the vector. --static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, -- const uint8_t seed[32]) { -- uint8_t input[33]; -- OPENSSL_memcpy(input, seed, 32); -- for (int i = 0; i < RANK; i++) { -- input[32] = (*counter)++; -- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); -- } ++} ++ +/************************************************* +* Name: poly_invntt_tomont +* @@ -1691,21 +1141,8 @@ index 776c085f9..ccb5b3d9b 100644 +static void poly_invntt_tomont(poly *r) +{ + invntt(r->coeffs); - } - --// Expands the matrix of a seed for key generation and for encaps-CPA. --static void matrix_expand(matrix *out, const uint8_t rho[32]) { -- uint8_t input[34]; -- OPENSSL_memcpy(input, rho, 32); -- for (int i = 0; i < RANK; i++) { -- for (int j = 0; j < RANK; j++) { -- input[32] = i; -- input[33] = j; -- struct BORINGSSL_keccak_st keccak_ctx; -- BORINGSSL_keccak_init(&keccak_ctx, input, sizeof(input), -- boringssl_shake128); -- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); -- } ++} ++ +/************************************************* +* Name: poly_basemul_montgomery +* @@ -1721,35 +1158,9 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;icoeffs[4*i], &a->coeffs[4*i], &b->coeffs[4*i], zetas[64+i]); + basemul(&r->coeffs[4*i+2], &a->coeffs[4*i+2], &b->coeffs[4*i+2], -zetas[64+i]); - } - } - --static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, -- 0x1f, 0x3f, 0x7f, 0xff}; -- --static void scalar_encode(uint8_t *out, const scalar *s, int bits) { -- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); -- -- uint8_t out_byte = 0; -- int out_byte_bits = 0; -- -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = s->c[i]; -- int element_bits_done = 0; -- -- while (element_bits_done < bits) { -- int chunk_bits = bits - element_bits_done; -- int out_bits_remaining = 8 - out_byte_bits; -- if (chunk_bits >= out_bits_remaining) { -- chunk_bits = out_bits_remaining; -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- *out = out_byte; -- out++; -- out_byte_bits = 0; -- out_byte = 0; -- } else { -- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; -- out_byte_bits += chunk_bits; ++ } ++} ++ +/************************************************* +* Name: poly_tomont +* @@ -1844,10 +1255,8 @@ index 776c085f9..ccb5b3d9b 100644 + d0 *= 645084; + d0 >>= 31; + t[k] = d0 & 0x7ff; - } - -- element_bits_done += chunk_bits; -- element >>= chunk_bits; ++ } ++ + r[ 0] = (t[0] >> 0); + r[ 1] = (t[0] >> 8) | (t[1] << 3); + r[ 2] = (t[1] >> 5) | (t[2] << 6); @@ -1860,8 +1269,8 @@ index 776c085f9..ccb5b3d9b 100644 + r[ 9] = (t[6] >> 6) | (t[7] << 5); + r[10] = (t[7] >> 3); + r += 11; - } - } ++ } ++ } +#elif (KYBER_POLYVECCOMPRESSEDBYTES == (KYBER_K * 320)) + uint16_t t[4]; + for(i=0;i>= 32; + t[k] = d0 & 0x3ff; + } - -- if (out_byte_bits > 0) { -- *out = out_byte; ++ + r[0] = (t[0] >> 0); + r[1] = (t[0] >> 8) | (t[1] << 2); + r[2] = (t[1] >> 6) | (t[2] << 4); @@ -1886,18 +1293,12 @@ index 776c085f9..ccb5b3d9b 100644 + r[4] = (t[3] >> 2); + r += 5; + } - } ++ } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif - } - --// scalar_encode_1 is |scalar_encode| specialised for |bits| == 1. --static void scalar_encode_1(uint8_t out[32], const scalar *s) { -- for (int i = 0; i < DEGREE; i += 8) { -- uint8_t out_byte = 0; -- for (int j = 0; j < 8; j++) { -- out_byte |= (s->c[i + j] & 1) << j; ++} ++ +/************************************************* +* Name: polyvec_decompress +* @@ -1942,22 +1343,13 @@ index 776c085f9..ccb5b3d9b 100644 + + for(k=0;k<4;k++) + r->vec[i].coeffs[4*j+k] = ((uint32_t)(t[k] & 0x3FF)*KYBER_Q + 512) >> 10; - } -- *out = out_byte; -- out++; - } ++ } ++ } +#else +#error "KYBER_POLYVECCOMPRESSEDBYTES needs to be in {320*KYBER_K, 352*KYBER_K}" +#endif - } - --// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 --// (DEGREE) is divisible by 8, the individual vector entries will always fill a --// whole number of bytes, so we do not need to worry about bit packing here. --static void vector_encode(uint8_t *out, const vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); -- } ++} ++ +/************************************************* +* Name: polyvec_tobytes +* @@ -1972,13 +1364,8 @@ index 776c085f9..ccb5b3d9b 100644 + unsigned int i; + for(i=0;ivec[i]); - } - --// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in --// |out|. It returns one on success and zero if any parsed value is >= --// |kPrime|. --static int scalar_decode(scalar *out, const uint8_t *in, int bits) { -- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); ++} ++ +/************************************************* +* Name: polyvec_frombytes +* @@ -1995,9 +1382,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i], a+i*KYBER_POLYBYTES); +} - -- uint8_t in_byte = 0; -- int in_byte_bits_left = 0; ++ +/************************************************* +* Name: polyvec_ntt +* @@ -2011,10 +1396,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} - -- for (int i = 0; i < DEGREE; i++) { -- uint16_t element = 0; -- int element_bits_done = 0; ++ +/************************************************* +* Name: polyvec_invntt_tomont +* @@ -2029,13 +1411,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} - -- while (element_bits_done < bits) { -- if (in_byte_bits_left == 0) { -- in_byte = *in; -- in++; -- in_byte_bits_left = 8; -- } ++ +/************************************************* +* Name: polyvec_basemul_acc_montgomery +* @@ -2056,17 +1432,10 @@ index 776c085f9..ccb5b3d9b 100644 + poly_basemul_montgomery(&t, &a->vec[i], &b->vec[i]); + poly_add(r, r, &t); + } - -- int chunk_bits = bits - element_bits_done; -- if (chunk_bits > in_byte_bits_left) { -- chunk_bits = in_byte_bits_left; -- } ++ + poly_reduce(r); +} - -- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; -- in_byte_bits_left -= chunk_bits; -- in_byte >>= chunk_bits; ++ +/************************************************* +* Name: polyvec_reduce +* @@ -2082,9 +1451,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i]); +} - -- element_bits_done += chunk_bits; -- } ++ +/************************************************* +* Name: polyvec_add +* @@ -2100,12 +1467,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ivec[i], &a->vec[i], &b->vec[i]); +} - -- if (element >= kPrime) { -- return 0; -- } -- out->c[i] = element; -- } ++ +// +// indcpa.c +// @@ -2154,21 +1516,12 @@ index 776c085f9..ccb5b3d9b 100644 + + if(verify(repacked, packedpk, KYBER_POLYVECBYTES) != 0) + return 0; - ++ + for(i=0;ic[i + j] = in_byte & 1; -- in_byte >>= 1; -- } ++ return 1; ++} ++ +/************************************************* +* Name: pack_sk +* @@ -2259,17 +1612,11 @@ index 776c085f9..ccb5b3d9b 100644 + r[ctr++] = val0; + if(ctr < len && val1 < KYBER_Q) + r[ctr++] = val1; - } ++ } + + return ctr; - } - --// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on --// success or zero if any parsed value is >= |kPrime|. --static int vector_decode(vector *out, const uint8_t *in, int bits) { -- for (int i = 0; i < RANK; i++) { -- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { -- return 0; ++} ++ +#define gen_a(A,B) gen_matrix(A,B,0) +#define gen_at(A,B) gen_matrix(A,B,1) + @@ -2313,52 +1660,10 @@ index 776c085f9..ccb5b3d9b 100644 + buflen = off + XOF_BLOCKBYTES; + ctr += rej_uniform(a[i].vec[j].coeffs + ctr, KYBER_N - ctr, buf, buflen); + } - } - } -- return 1; - } - --// Compresses (lossily) an input |x| mod 3329 into |bits| many bits by grouping --// numbers close to each other together. The formula used is --// round(2^|bits|/kPrime*x) mod 2^|bits|. --// Uses Barrett reduction to achieve constant time. Since we need both the --// remainder (for rounding) and the quotient (as the result), we cannot use --// |reduce| here, but need to do the Barrett reduction directly. --static uint16_t compress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x << bits; -- uint32_t quotient = ((uint64_t)product * kBarrettMultiplier) >> kBarrettShift; -- uint32_t remainder = product - quotient * kPrime; -- -- // Adjust the quotient to round correctly: -- // 0 <= remainder <= kHalfPrime round to 0 -- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 -- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 -- assert(remainder < 2u * kPrime); -- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); -- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); -- return quotient & ((1 << bits) - 1); --} -- --// Decompresses |x| by using an equi-distant representative. The formula is --// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to --// implement this logic using only bit operations. --static uint16_t decompress(uint16_t x, int bits) { -- uint32_t product = (uint32_t)x * kPrime; -- uint32_t power = 1 << bits; -- // This is |product| % power, since |power| is a power of 2. -- uint32_t remainder = product & (power - 1); -- // This is |product| / power, since |power| is a power of 2. -- uint32_t lower = product >> bits; -- // The rounding logic works since the first half of numbers mod |power| have a -- // 0 as first bit, and the second half has a 1 as first bit, since |power| is -- // a power of 2. As a 12 bit number, |remainder| is always positive, so we -- // will shift in 0s for a right shift. -- return lower + (remainder >> (bits - 1)); --} -- --static void scalar_compress(scalar *s, int bits) { -- for (int i = 0; i < DEGREE; i++) { -- s->c[i] = compress(s->c[i], bits); ++ } ++ } ++} ++ +/************************************************* +* Name: indcpa_keypair +* @@ -2398,19 +1703,15 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;ic[i] = decompress(s->c[i], bits); -- } ++} ++ +/************************************************* +* Name: indcpa_enc +* @@ -2469,12 +1770,8 @@ index 776c085f9..ccb5b3d9b 100644 + + pack_ciphertext(c, &b, &v); + return 1; - } - --static void vector_compress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_compress(&a->v[i], bits); -- } ++} ++ +/************************************************* +* Name: indcpa_dec +* @@ -2506,12 +1803,8 @@ index 776c085f9..ccb5b3d9b 100644 + poly_reduce(&mp); + + poly_tomsg(m, &mp); - } - --static void vector_decompress(vector *a, int bits) { -- for (int i = 0; i < RANK; i++) { -- scalar_decompress(&a->v[i], bits); -- } ++} ++ +// +// fips202.c +// @@ -2541,13 +1834,8 @@ index 776c085f9..ccb5b3d9b 100644 + r |= (uint64_t)x[i] << 8*i; + + return r; - } - --struct public_key { -- vector t; -- uint8_t rho[32]; -- uint8_t public_key_hash[32]; -- matrix m; ++} ++ +/************************************************* +* Name: store64 +* @@ -2591,13 +1879,16 @@ index 776c085f9..ccb5b3d9b 100644 + (uint64_t)0x8000000080008008ULL }; --static struct public_key *public_key_from_external( -- const struct KYBER_public_key *external) { -- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), -- "Kyber public key is too small"); -- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), -- "Kyber public key align incorrect"); -- return (struct public_key *)external; +-// reduce_once reduces 0 <= x < 2*kPrime, mod kPrime. +-static uint16_t reduce_once(uint16_t x) { +- assert(x < 2 * kPrime); +- const uint16_t subtracted = x - kPrime; +- uint16_t mask = 0u - (subtracted >> 15); +- // On Aarch64, omitting a |value_barrier_u16| results in a 2x speedup of Kyber +- // overall and Clang still produces constant-time code using `csel`. On other +- // platforms & compilers on godbolt that we care about, this code also +- // produces constant-time output. +- return (mask & x) | (~mask & subtracted); +/************************************************* +* Name: KeccakF1600_StatePermute +* @@ -2869,36 +2160,17 @@ index 776c085f9..ccb5b3d9b 100644 + state[24] = Asu; } --struct private_key { -- struct public_key pub; -- vector s; -- uint8_t fo_failure_secret[32]; --}; +-// constant time reduce x mod kPrime using Barrett reduction. x must be less +-// than kPrime + 2×kPrime². +-static uint16_t reduce(uint32_t x) { +- assert(x < kPrime + 2u * kPrime * kPrime); +- uint64_t product = (uint64_t)x * kBarrettMultiplier; +- uint32_t quotient = (uint32_t)(product >> kBarrettShift); +- uint32_t remainder = x - quotient * kPrime; +- return reduce_once(remainder); +-} --static struct private_key *private_key_from_external( -- const struct KYBER_private_key *external) { -- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), -- "Kyber private key too small"); -- static_assert( -- alignof(struct KYBER_private_key) >= alignof(struct private_key), -- "Kyber private key align incorrect"); -- return (struct private_key *)external; --} -- --// Calls |KYBER_generate_key_external_entropy| with random bytes from --// |RAND_bytes|. --void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], -- struct KYBER_private_key *out_private_key) { -- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; -- RAND_bytes(entropy, sizeof(entropy)); -- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, -- entropy); --} -- --static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { -- uint8_t *vector_output; -- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { -- return 0; +-static void scalar_zero(scalar *out) { OPENSSL_memset(out, 0, sizeof(*out)); } +/************************************************* +* Name: keccak_squeeze +* @@ -2921,20 +2193,41 @@ index 776c085f9..ccb5b3d9b 100644 + unsigned int r) +{ + unsigned int i; -+ + +-static void vector_zero(vector *out) { OPENSSL_memset(out, 0, sizeof(*out)); } +- +-// In place number theoretic transform of a given scalar. +-// Note that Kyber's kPrime 3329 does not have a 512th root of unity, so this +-// transform leaves off the last iteration of the usual FFT code, with the 128 +-// relevant roots of unity being stored in |kNTTRoots|. This means the output +-// should be seen as 128 elements in GF(3329^2), with the coefficients of the +-// elements being consecutive entries in |s->c|. +-static void scalar_ntt(scalar *s) { +- int offset = DEGREE; +- // `int` is used here because using `size_t` throughout caused a ~5% slowdown +- // with Clang 14 on Aarch64. +- for (int step = 1; step < DEGREE / 2; step <<= 1) { +- offset >>= 1; +- int k = 0; +- for (int i = 0; i < step; i++) { +- const uint32_t step_root = kNTTRoots[i + step]; +- for (int j = k; j < k + offset; j++) { +- uint16_t odd = reduce(step_root * s->c[j + offset]); +- uint16_t even = s->c[j]; +- s->c[j] = reduce_once(odd + even); +- s->c[j + offset] = reduce_once(even - odd + kPrime); +- } +- k += 2 * offset; + while(outlen) { + if(pos == r) { + KeccakF1600_StatePermute(s); + pos = 0; -+ } + } + for(i=pos;i < r && i < pos+outlen; i++) + *out++ = s[i/8] >> 8*(i%8); + outlen -= i-pos; + pos = i; - } -- vector_encode(vector_output, &pub->t, kLog2Prime); -- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { -- return 0; ++ } + + return pos; +} @@ -2966,8 +2259,7 @@ index 776c085f9..ccb5b3d9b 100644 + inlen -= r-pos; + KeccakF1600_StatePermute(s); + pos = 0; - } -- return 1; ++ } + + for(i=pos;ipub.rho, hashed, sizeof(priv->pub.rho)); -- matrix_expand(&priv->pub.m, rho); -- uint8_t counter = 0; -- vector_generate_secret_eta_2(&priv->s, &counter, sigma); -- vector_ntt(&priv->s); -- vector error; -- vector_generate_secret_eta_2(&error, &counter, sigma); -- vector_ntt(&error); -- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); -- vector_add(&priv->pub.t, &error); -- -- CBB cbb; -- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); -- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { -- abort(); ++} ++ + +/************************************************* +* Name: keccak_absorb_once @@ -3048,8 +2313,824 @@ index 776c085f9..ccb5b3d9b 100644 + in += r; + inlen -= r; + KeccakF1600_StatePermute(s); ++ } ++ ++ for(i=0;iv[i]); +- } ++ ++/************************************************* ++* Name: shake128_absorb_once ++* ++* Description: Initialize, absorb into and finalize SHAKE128 XOF; non-incremental. ++* ++* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state ++* - const uint8_t *in: pointer to input to be absorbed into s ++* - size_t inlen: length of input in bytes ++**************************************************/ ++static void shake128_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) ++{ ++ keccak_absorb_once(state->s, SHAKE128_RATE, in, inlen, 0x1F); ++ state->pos = SHAKE128_RATE; + } + +-// In place inverse number theoretic transform of a given scalar, with pairs of +-// entries of s->v being interpreted as elements of GF(3329^2). Just as with the +-// number theoretic transform, this leaves off the first step of the normal iFFT +-// to account for the fact that 3329 does not have a 512th root of unity, using +-// the precomputed 128 roots of unity stored in |kInverseNTTRoots|. +-static void scalar_inverse_ntt(scalar *s) { +- int step = DEGREE / 2; +- // `int` is used here because using `size_t` throughout caused a ~5% slowdown +- // with Clang 14 on Aarch64. +- for (int offset = 2; offset < DEGREE; offset <<= 1) { +- step >>= 1; +- int k = 0; +- for (int i = 0; i < step; i++) { +- uint32_t step_root = kInverseNTTRoots[i + step]; +- for (int j = k; j < k + offset; j++) { +- uint16_t odd = s->c[j + offset]; +- uint16_t even = s->c[j]; +- s->c[j] = reduce_once(odd + even); +- s->c[j + offset] = reduce(step_root * (even - odd + kPrime)); +- } +- k += 2 * offset; +- } +- } +- for (int i = 0; i < DEGREE; i++) { +- s->c[i] = reduce(s->c[i] * kInverseDegree); +- } ++/************************************************* ++* Name: shake128_squeezeblocks ++* ++* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of ++* SHAKE128_RATE bytes each. Can be called multiple times ++* to keep squeezing. Assumes new block has not yet been ++* started (state->pos = SHAKE128_RATE). ++* ++* Arguments: - uint8_t *out: pointer to output blocks ++* - size_t nblocks: number of blocks to be squeezed (written to output) ++* - keccak_state *s: pointer to input/output Keccak state ++**************************************************/ ++static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) ++{ ++ keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); + } + +-static void vector_inverse_ntt(vector *a) { +- for (int i = 0; i < RANK; i++) { +- scalar_inverse_ntt(&a->v[i]); +- } ++/************************************************* ++* Name: shake256_squeeze ++* ++* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many ++* bytes. Can be called multiple times to keep squeezing. ++* ++* Arguments: - uint8_t *out: pointer to output blocks ++* - size_t outlen : number of bytes to be squeezed (written to output) ++* - keccak_state *s: pointer to input/output Keccak state ++**************************************************/ ++static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) ++{ ++ state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); + } + +-static void scalar_add(scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE; i++) { +- lhs->c[i] = reduce_once(lhs->c[i] + rhs->c[i]); +- } ++/************************************************* ++* Name: shake256_absorb_once ++* ++* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental. ++* ++* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state ++* - const uint8_t *in: pointer to input to be absorbed into s ++* - size_t inlen: length of input in bytes ++**************************************************/ ++static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) ++{ ++ keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); ++ state->pos = SHAKE256_RATE; + } + +-static void scalar_sub(scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE; i++) { +- lhs->c[i] = reduce_once(lhs->c[i] - rhs->c[i] + kPrime); +- } ++/************************************************* ++* Name: shake256_squeezeblocks ++* ++* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of ++* SHAKE256_RATE bytes each. Can be called multiple times ++* to keep squeezing. Assumes next block has not yet been ++* started (state->pos = SHAKE256_RATE). ++* ++* Arguments: - uint8_t *out: pointer to output blocks ++* - size_t nblocks: number of blocks to be squeezed (written to output) ++* - keccak_state *s: pointer to input/output Keccak state ++**************************************************/ ++static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) ++{ ++ keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); + } + +-// Multiplying two scalars in the number theoretically transformed state. Since +-// 3329 does not have a 512th root of unity, this means we have to interpret +-// the 2*ith and (2*i+1)th entries of the scalar as elements of GF(3329)[X]/(X^2 +-// - 17^(2*bitreverse(i)+1)) The value of 17^(2*bitreverse(i)+1) mod 3329 is +-// stored in the precomputed |kModRoots| table. Note that our Barrett transform +-// only allows us to multipy two reduced numbers together, so we need some +-// intermediate reduction steps, even if an uint64_t could hold 3 multiplied +-// numbers. +-static void scalar_mult(scalar *out, const scalar *lhs, const scalar *rhs) { +- for (int i = 0; i < DEGREE / 2; i++) { +- uint32_t real_real = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i]; +- uint32_t img_img = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i + 1]; +- uint32_t real_img = (uint32_t)lhs->c[2 * i] * rhs->c[2 * i + 1]; +- uint32_t img_real = (uint32_t)lhs->c[2 * i + 1] * rhs->c[2 * i]; +- out->c[2 * i] = +- reduce(real_real + (uint32_t)reduce(img_img) * kModRoots[i]); +- out->c[2 * i + 1] = reduce(img_real + real_img); +- } ++/************************************************* ++* Name: shake256_absorb ++* ++* Description: Absorb step of the SHAKE256 XOF; incremental. ++* ++* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state ++* - const uint8_t *in: pointer to input to be absorbed into s ++* - size_t inlen: length of input in bytes ++**************************************************/ ++static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) ++{ ++ state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); + } + +-static void vector_add(vector *lhs, const vector *rhs) { +- for (int i = 0; i < RANK; i++) { +- scalar_add(&lhs->v[i], &rhs->v[i]); +- } ++/************************************************* ++* Name: shake256_finalize ++* ++* Description: Finalize absorb step of the SHAKE256 XOF. ++* ++* Arguments: - keccak_state *state: pointer to Keccak state ++**************************************************/ ++static void shake256_finalize(keccak_state *state) ++{ ++ keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); ++ state->pos = SHAKE256_RATE; + } + +-static void matrix_mult(vector *out, const matrix *m, const vector *a) { +- vector_zero(out); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- scalar product; +- scalar_mult(&product, &m->v[i][j], &a->v[j]); +- scalar_add(&out->v[i], &product); +- } +- } ++/************************************************* ++* Name: keccak_init ++* ++* Description: Initializes the Keccak state. ++* ++* Arguments: - uint64_t *s: pointer to Keccak state ++**************************************************/ ++static void keccak_init(uint64_t s[25]) ++{ ++ unsigned int i; ++ for(i=0;i<25;i++) ++ s[i] = 0; + } + +-static void matrix_mult_transpose(vector *out, const matrix *m, +- const vector *a) { +- vector_zero(out); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- scalar product; +- scalar_mult(&product, &m->v[j][i], &a->v[j]); +- scalar_add(&out->v[i], &product); +- } +- } ++/************************************************* ++* Name: shake256_init ++* ++* Description: Initilizes Keccak state for use as SHAKE256 XOF ++* ++* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state ++**************************************************/ ++static void shake256_init(keccak_state *state) ++{ ++ keccak_init(state->s); ++ state->pos = 0; + } + +-static void scalar_inner_product(scalar *out, const vector *lhs, +- const vector *rhs) { +- scalar_zero(out); +- for (int i = 0; i < RANK; i++) { +- scalar product; +- scalar_mult(&product, &lhs->v[i], &rhs->v[i]); +- scalar_add(out, &product); +- } ++ ++/************************************************* ++* Name: shake256 ++* ++* Description: SHAKE256 XOF with non-incremental API ++* ++* Arguments: - uint8_t *out: pointer to output ++* - size_t outlen: requested output length in bytes ++* - const uint8_t *in: pointer to input ++* - size_t inlen: length of input in bytes ++**************************************************/ ++static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) ++{ ++ size_t nblocks; ++ keccak_state state; ++ ++ shake256_absorb_once(&state, in, inlen); ++ nblocks = outlen/SHAKE256_RATE; ++ shake256_squeezeblocks(out, nblocks, &state); ++ outlen -= nblocks*SHAKE256_RATE; ++ out += nblocks*SHAKE256_RATE; ++ shake256_squeeze(out, outlen, &state); + } + +-// Algorithm 1 of the Kyber spec. Rejection samples a Keccak stream to get +-// uniformly distributed elements. This is used for matrix expansion and only +-// operates on public inputs. +-static void scalar_from_keccak_vartime(scalar *out, +- struct BORINGSSL_keccak_st *keccak_ctx) { +- assert(keccak_ctx->squeeze_offset == 0); +- assert(keccak_ctx->rate_bytes == 168); +- static_assert(168 % 3 == 0, "block and coefficient boundaries do not align"); ++/************************************************* ++* Name: sha3_256 ++* ++* Description: SHA3-256 with non-incremental API ++* ++* Arguments: - uint8_t *h: pointer to output (32 bytes) ++* - const uint8_t *in: pointer to input ++* - size_t inlen: length of input in bytes ++**************************************************/ ++static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) ++{ ++ unsigned int i; ++ uint64_t s[25]; + +- int done = 0; +- while (done < DEGREE) { +- uint8_t block[168]; +- BORINGSSL_keccak_squeeze(keccak_ctx, block, sizeof(block)); +- for (size_t i = 0; i < sizeof(block) && done < DEGREE; i += 3) { +- uint16_t d1 = block[i] + 256 * (block[i + 1] % 16); +- uint16_t d2 = block[i + 1] / 16 + 16 * block[i + 2]; +- if (d1 < kPrime) { +- out->c[done++] = d1; +- } +- if (d2 < kPrime && done < DEGREE) { +- out->c[done++] = d2; +- } +- } +- } ++ keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); ++ KeccakF1600_StatePermute(s); ++ for(i=0;i<4;i++) ++ store64(h+8*i,s[i]); + } + +-// Algorithm 2 of the Kyber spec, with eta fixed to two and the PRF call +-// included. Creates binominally distributed elements by sampling 2*|eta| bits, +-// and setting the coefficient to the count of the first bits minus the count of +-// the second bits, resulting in a centered binomial distribution. Since eta is +-// two this gives -2/2 with a probability of 1/16, -1/1 with probability 1/4, +-// and 0 with probability 3/8. +-static void scalar_centered_binomial_distribution_eta_2_with_prf( +- scalar *out, const uint8_t input[33]) { +- uint8_t entropy[128]; +- static_assert(sizeof(entropy) == 2 * /*kEta=*/2 * DEGREE / 8, ""); +- BORINGSSL_keccak(entropy, sizeof(entropy), input, 33, boringssl_shake256); ++/************************************************* ++* Name: sha3_512 ++* ++* Description: SHA3-512 with non-incremental API ++* ++* Arguments: - uint8_t *h: pointer to output (64 bytes) ++* - const uint8_t *in: pointer to input ++* - size_t inlen: length of input in bytes ++**************************************************/ ++static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) ++{ ++ unsigned int i; ++ uint64_t s[25]; + +- for (int i = 0; i < DEGREE; i += 2) { +- uint8_t byte = entropy[i / 2]; +- +- uint16_t value = kPrime; +- value += (byte & 1) + ((byte >> 1) & 1); +- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); +- out->c[i] = reduce_once(value); +- +- byte >>= 4; +- value = kPrime; +- value += (byte & 1) + ((byte >> 1) & 1); +- value -= ((byte >> 2) & 1) + ((byte >> 3) & 1); +- out->c[i + 1] = reduce_once(value); +- } ++ keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); ++ KeccakF1600_StatePermute(s); ++ for(i=0;i<8;i++) ++ store64(h+8*i,s[i]); + } + +-// Generates a secret vector by using +-// |scalar_centered_binomial_distribution_eta_2_with_prf|, using the given seed +-// appending and incrementing |counter| for entry of the vector. +-static void vector_generate_secret_eta_2(vector *out, uint8_t *counter, +- const uint8_t seed[32]) { +- uint8_t input[33]; +- OPENSSL_memcpy(input, seed, 32); +- for (int i = 0; i < RANK; i++) { +- input[32] = (*counter)++; +- scalar_centered_binomial_distribution_eta_2_with_prf(&out->v[i], input); +- } ++// ++// symmetric-shake.c ++// ++ ++/************************************************* ++* Name: kyber_shake128_absorb ++* ++* Description: Absorb step of the SHAKE128 specialized for the Kyber context. ++* ++* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state ++* - const uint8_t *seed: pointer to KYBER_SYMBYTES input to be absorbed into state ++* - uint8_t i: additional byte of input ++* - uint8_t j: additional byte of input ++**************************************************/ ++static void kyber_shake128_absorb(keccak_state *state, ++ const uint8_t seed[KYBER_SYMBYTES], ++ uint8_t x, ++ uint8_t y) ++{ ++ uint8_t extseed[KYBER_SYMBYTES+2]; ++ ++ memcpy(extseed, seed, KYBER_SYMBYTES); ++ extseed[KYBER_SYMBYTES+0] = x; ++ extseed[KYBER_SYMBYTES+1] = y; ++ ++ shake128_absorb_once(state, extseed, sizeof(extseed)); + } + +-// Expands the matrix of a seed for key generation and for encaps-CPA. +-static void matrix_expand(matrix *out, const uint8_t rho[32]) { +- uint8_t input[34]; +- OPENSSL_memcpy(input, rho, 32); +- for (int i = 0; i < RANK; i++) { +- for (int j = 0; j < RANK; j++) { +- input[32] = i; +- input[33] = j; +- struct BORINGSSL_keccak_st keccak_ctx; +- BORINGSSL_keccak_init(&keccak_ctx, boringssl_shake128); +- BORINGSSL_keccak_absorb(&keccak_ctx, input, sizeof(input)); +- scalar_from_keccak_vartime(&out->v[i][j], &keccak_ctx); +- } +- } ++/************************************************* ++* Name: kyber_shake256_prf ++* ++* Description: Usage of SHAKE256 as a PRF, concatenates secret and public input ++* and then generates outlen bytes of SHAKE256 output ++* ++* Arguments: - uint8_t *out: pointer to output ++* - size_t outlen: number of requested output bytes ++* - const uint8_t *key: pointer to the key (of length KYBER_SYMBYTES) ++* - uint8_t nonce: single-byte nonce (public PRF input) ++**************************************************/ ++static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce) ++{ ++ uint8_t extkey[KYBER_SYMBYTES+1]; ++ ++ memcpy(extkey, key, KYBER_SYMBYTES); ++ extkey[KYBER_SYMBYTES] = nonce; ++ ++ shake256(out, outlen, extkey, sizeof(extkey)); + } + +-static const uint8_t kMasks[8] = {0x01, 0x03, 0x07, 0x0f, +- 0x1f, 0x3f, 0x7f, 0xff}; ++// ++// kem.c ++// + +-static void scalar_encode(uint8_t *out, const scalar *s, int bits) { +- assert(bits <= (int)sizeof(*s->c) * 8 && bits != 1); ++// Modified crypto_kem_keypair to BoringSSL style API ++void generate_key(struct public_key *out_pub, struct private_key *out_priv, ++ const uint8_t seed[KYBER_GENERATE_KEY_BYTES]) ++{ ++ size_t i; ++ uint8_t* pk = &out_pub->opaque[0]; ++ uint8_t* sk = &out_priv->opaque[0]; + +- uint8_t out_byte = 0; +- int out_byte_bits = 0; +- +- for (int i = 0; i < DEGREE; i++) { +- uint16_t element = s->c[i]; +- int element_bits_done = 0; +- +- while (element_bits_done < bits) { +- int chunk_bits = bits - element_bits_done; +- int out_bits_remaining = 8 - out_byte_bits; +- if (chunk_bits >= out_bits_remaining) { +- chunk_bits = out_bits_remaining; +- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; +- *out = out_byte; +- out++; +- out_byte_bits = 0; +- out_byte = 0; +- } else { +- out_byte |= (element & kMasks[chunk_bits - 1]) << out_byte_bits; +- out_byte_bits += chunk_bits; +- } +- +- element_bits_done += chunk_bits; +- element >>= chunk_bits; +- } +- } +- +- if (out_byte_bits > 0) { +- *out = out_byte; +- } ++ indcpa_keypair(pk, sk, seed); ++ for(i=0;ic[i + j] & 1) << j; +- } +- *out = out_byte; +- out++; +- } +-} ++// Modified crypto_kem_enc to BoringSSL style API ++int encap(uint8_t out_ciphertext[KYBER_CIPHERTEXTBYTES], ++ uint8_t ss[KYBER_KEY_BYTES], ++ const struct public_key *in_pub, ++ const uint8_t seed[KYBER_ENCAP_BYTES], int mlkem) ++{ ++ const uint8_t *pk = &in_pub->opaque[0]; ++ uint8_t *ct = out_ciphertext; ++ ++ uint8_t buf[2*KYBER_SYMBYTES]; ++ /* Will contain key, coins */ ++ uint8_t kr[2*KYBER_SYMBYTES]; + +-// Encodes an entire vector into 32*|RANK|*|bits| bytes. Note that since 256 +-// (DEGREE) is divisible by 8, the individual vector entries will always fill a +-// whole number of bytes, so we do not need to worry about bit packing here. +-static void vector_encode(uint8_t *out, const vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_encode(out + i * bits * DEGREE / 8, &a->v[i], bits); +- } +-} ++ memcpy(buf, seed, KYBER_SYMBYTES); + +-// scalar_decode parses |DEGREE * bits| bits from |in| into |DEGREE| values in +-// |out|. It returns one on success and zero if any parsed value is >= +-// |kPrime|. +-static int scalar_decode(scalar *out, const uint8_t *in, int bits) { +- assert(bits <= (int)sizeof(*out->c) * 8 && bits != 1); ++ /* Don't release system RNG output */ ++ hash_h(buf, buf, KYBER_SYMBYTES); + +- uint8_t in_byte = 0; +- int in_byte_bits_left = 0; ++ /* Multitarget countermeasure for coins + contributory KEM */ ++ hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); ++ hash_g(kr, buf, 2*KYBER_SYMBYTES); + +- for (int i = 0; i < DEGREE; i++) { +- uint16_t element = 0; +- int element_bits_done = 0; ++ /* coins are in kr+KYBER_SYMBYTES */ ++ if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) ++ return 0; + +- while (element_bits_done < bits) { +- if (in_byte_bits_left == 0) { +- in_byte = *in; +- in++; +- in_byte_bits_left = 8; +- } +- +- int chunk_bits = bits - element_bits_done; +- if (chunk_bits > in_byte_bits_left) { +- chunk_bits = in_byte_bits_left; +- } +- +- element |= (in_byte & kMasks[chunk_bits - 1]) << element_bits_done; +- in_byte_bits_left -= chunk_bits; +- in_byte >>= chunk_bits; +- +- element_bits_done += chunk_bits; +- } +- +- if (element >= kPrime) { +- return 0; +- } +- out->c[i] = element; +- } +- +- return 1; +-} +- +-// scalar_decode_1 is |scalar_decode| specialised for |bits| == 1. +-static void scalar_decode_1(scalar *out, const uint8_t in[32]) { +- for (int i = 0; i < DEGREE; i += 8) { +- uint8_t in_byte = *in; +- in++; +- for (int j = 0; j < 8; j++) { +- out->c[i + j] = in_byte & 1; +- in_byte >>= 1; +- } +- } +-} +- +-// Decodes 32*|RANK|*|bits| bytes from |in| into |out|. It returns one on +-// success or zero if any parsed value is >= |kPrime|. +-static int vector_decode(vector *out, const uint8_t *in, int bits) { +- for (int i = 0; i < RANK; i++) { +- if (!scalar_decode(&out->v[i], in + i * bits * DEGREE / 8, bits)) { +- return 0; +- } ++ if (mlkem == 1) { ++ memcpy(ss, kr, KYBER_SYMBYTES); ++ } else { ++ /* overwrite coins in kr with H(c) */ ++ hash_h(kr+KYBER_SYMBYTES, ct, KYBER_CIPHERTEXTBYTES); ++ /* hash concatenation of pre-k and H(c) to k */ ++ kdf(ss, kr, 2*KYBER_SYMBYTES); + } + return 1; + } + +-// Compresses (lossily) an input |x| mod 3329 into |bits| many bits by grouping +-// numbers close to each other together. The formula used is +-// round(2^|bits|/kPrime*x) mod 2^|bits|. +-// Uses Barrett reduction to achieve constant time. Since we need both the +-// remainder (for rounding) and the quotient (as the result), we cannot use +-// |reduce| here, but need to do the Barrett reduction directly. +-static uint16_t compress(uint16_t x, int bits) { +- uint32_t shifted = (uint32_t)x << bits; +- uint64_t product = (uint64_t)shifted * kBarrettMultiplier; +- uint32_t quotient = (uint32_t)(product >> kBarrettShift); +- uint32_t remainder = shifted - quotient * kPrime; ++// Modified crypto_kem_decap to BoringSSL style API ++void decap(uint8_t out_shared_key[KYBER_SSBYTES], ++ const struct private_key *in_priv, ++ const uint8_t *ct, size_t ciphertext_len, int mlkem) ++{ ++ uint8_t *ss = out_shared_key; ++ const uint8_t *sk = &in_priv->opaque[0]; + +- // Adjust the quotient to round correctly: +- // 0 <= remainder <= kHalfPrime round to 0 +- // kHalfPrime < remainder <= kPrime + kHalfPrime round to 1 +- // kPrime + kHalfPrime < remainder < 2 * kPrime round to 2 +- assert(remainder < 2u * kPrime); +- quotient += 1 & constant_time_lt_w(kHalfPrime, remainder); +- quotient += 1 & constant_time_lt_w(kPrime + kHalfPrime, remainder); +- return quotient & ((1 << bits) - 1); +-} ++ size_t i; ++ int fail = 1; ++ uint8_t buf[2*KYBER_SYMBYTES]; ++ /* Will contain key, coins */ ++ uint8_t kr[2*KYBER_SYMBYTES]; ++ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; ++ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; + +-// Decompresses |x| by using an equi-distant representative. The formula is +-// round(kPrime/2^|bits|*x). Note that 2^|bits| being the divisor allows us to +-// implement this logic using only bit operations. +-static uint16_t decompress(uint16_t x, int bits) { +- uint32_t product = (uint32_t)x * kPrime; +- uint32_t power = 1 << bits; +- // This is |product| % power, since |power| is a power of 2. +- uint32_t remainder = product & (power - 1); +- // This is |product| / power, since |power| is a power of 2. +- uint32_t lower = product >> bits; +- // The rounding logic works since the first half of numbers mod |power| have a +- // 0 as first bit, and the second half has a 1 as first bit, since |power| is +- // a power of 2. As a 12 bit number, |remainder| is always positive, so we +- // will shift in 0s for a right shift. +- return lower + (remainder >> (bits - 1)); +-} ++ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { ++ indcpa_dec(buf, ct, sk); + +-static void scalar_compress(scalar *s, int bits) { +- for (int i = 0; i < DEGREE; i++) { +- s->c[i] = compress(s->c[i], bits); ++ /* Multitarget countermeasure for coins + contributory KEM */ ++ for(i=0;ic[i] = decompress(s->c[i], bits); +- } ++void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], ++ const struct public_key *in_pub) { ++ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); + } + +-static void vector_compress(vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_compress(&a->v[i], bits); +- } +-} +- +-static void vector_decompress(vector *a, int bits) { +- for (int i = 0; i < RANK; i++) { +- scalar_decompress(&a->v[i], bits); +- } +-} +- +-struct public_key { +- vector t; +- uint8_t rho[32]; +- uint8_t public_key_hash[32]; +- matrix m; +-}; +- +-static struct public_key *public_key_from_external( +- const struct KYBER_public_key *external) { +- static_assert(sizeof(struct KYBER_public_key) >= sizeof(struct public_key), +- "Kyber public key is too small"); +- static_assert(alignof(struct KYBER_public_key) >= alignof(struct public_key), +- "Kyber public key align incorrect"); +- return (struct public_key *)external; +-} +- +-struct private_key { +- struct public_key pub; +- vector s; +- uint8_t fo_failure_secret[32]; +-}; +- +-static struct private_key *private_key_from_external( +- const struct KYBER_private_key *external) { +- static_assert(sizeof(struct KYBER_private_key) >= sizeof(struct private_key), +- "Kyber private key too small"); +- static_assert( +- alignof(struct KYBER_private_key) >= alignof(struct private_key), +- "Kyber private key align incorrect"); +- return (struct private_key *)external; +-} +- +-// Calls |KYBER_generate_key_external_entropy| with random bytes from +-// |RAND_bytes|. +-void KYBER_generate_key(uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], +- struct KYBER_private_key *out_private_key) { +- uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]; +- RAND_bytes(entropy, sizeof(entropy)); +- KYBER_generate_key_external_entropy(out_encoded_public_key, out_private_key, +- entropy); +-} +- +-static int kyber_marshal_public_key(CBB *out, const struct public_key *pub) { +- uint8_t *vector_output; +- if (!CBB_add_space(out, &vector_output, kEncodedVectorSize)) { +- return 0; +- } +- vector_encode(vector_output, &pub->t, kLog2Prime); +- if (!CBB_add_bytes(out, pub->rho, sizeof(pub->rho))) { +- return 0; +- } +- return 1; +-} +- +-// Algorithms 4 and 7 of the Kyber spec. Algorithms are combined since key +-// generation is not part of the FO transform, and the spec uses Algorithm 7 to +-// specify the actual key format. +-void KYBER_generate_key_external_entropy( +- uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], +- struct KYBER_private_key *out_private_key, +- const uint8_t entropy[KYBER_GENERATE_KEY_ENTROPY]) { +- struct private_key *priv = private_key_from_external(out_private_key); +- uint8_t hashed[64]; +- BORINGSSL_keccak(hashed, sizeof(hashed), entropy, 32, boringssl_sha3_512); +- const uint8_t *const rho = hashed; +- const uint8_t *const sigma = hashed + 32; +- OPENSSL_memcpy(priv->pub.rho, hashed, sizeof(priv->pub.rho)); +- matrix_expand(&priv->pub.m, rho); +- uint8_t counter = 0; +- vector_generate_secret_eta_2(&priv->s, &counter, sigma); +- vector_ntt(&priv->s); +- vector error; +- vector_generate_secret_eta_2(&error, &counter, sigma); +- vector_ntt(&error); +- matrix_mult_transpose(&priv->pub.t, &priv->pub.m, &priv->s); +- vector_add(&priv->pub.t, &error); +- +- CBB cbb; +- CBB_init_fixed(&cbb, out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES); +- if (!kyber_marshal_public_key(&cbb, &priv->pub)) { +- abort(); +- } +- - BORINGSSL_keccak(priv->pub.public_key_hash, sizeof(priv->pub.public_key_hash), - out_encoded_public_key, KYBER_PUBLIC_KEY_BYTES, - boringssl_sha3_256); @@ -3180,65 +3261,18 @@ index 776c085f9..ccb5b3d9b 100644 - for (int i = 0; i < 32; i++) { - input[i] = constant_time_select_8(mask, prekey_and_randomness[i], - priv->fo_failure_secret[i]); -+ for(i=0;is, SHAKE128_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE128_RATE; - } - +-} +- -// kyber_parse_public_key_no_hash parses |in| into |pub| but doesn't calculate -// the value of |pub->public_key_hash|. -static int kyber_parse_public_key_no_hash(struct public_key *pub, CBS *in) { @@ -3250,336 +3284,27 @@ index 776c085f9..ccb5b3d9b 100644 - } - matrix_expand(&pub->m, pub->rho); - return 1; -+/************************************************* -+* Name: shake128_squeezeblocks -+* -+* Description: Squeeze step of SHAKE128 XOF. Squeezes full blocks of -+* SHAKE128_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes new block has not yet been -+* started (state->pos = SHAKE128_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake128_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE128_RATE); -+} -+ -+/************************************************* -+* Name: shake256_squeeze -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes arbitraily many -+* bytes. Can be called multiple times to keep squeezing. -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t outlen : number of bytes to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeeze(uint8_t *out, size_t outlen, keccak_state *state) -+{ -+ state->pos = keccak_squeeze(out, outlen, state->s, state->pos, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb_once -+* -+* Description: Initialize, absorb into and finalize SHAKE256 XOF; non-incremental. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb_once(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ keccak_absorb_once(state->s, SHAKE256_RATE, in, inlen, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: shake256_squeezeblocks -+* -+* Description: Squeeze step of SHAKE256 XOF. Squeezes full blocks of -+* SHAKE256_RATE bytes each. Can be called multiple times -+* to keep squeezing. Assumes next block has not yet been -+* started (state->pos = SHAKE256_RATE). -+* -+* Arguments: - uint8_t *out: pointer to output blocks -+* - size_t nblocks: number of blocks to be squeezed (written to output) -+* - keccak_state *s: pointer to input/output Keccak state -+**************************************************/ -+static void shake256_squeezeblocks(uint8_t *out, size_t nblocks, keccak_state *state) -+{ -+ keccak_squeezeblocks(out, nblocks, state->s, SHAKE256_RATE); -+} -+ -+/************************************************* -+* Name: shake256_absorb -+* -+* Description: Absorb step of the SHAKE256 XOF; incremental. -+* -+* Arguments: - keccak_state *state: pointer to (initialized) output Keccak state -+* - const uint8_t *in: pointer to input to be absorbed into s -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256_absorb(keccak_state *state, const uint8_t *in, size_t inlen) -+{ -+ state->pos = keccak_absorb(state->s, state->pos, SHAKE256_RATE, in, inlen); -+} -+ -+/************************************************* -+* Name: shake256_finalize -+* -+* Description: Finalize absorb step of the SHAKE256 XOF. -+* -+* Arguments: - keccak_state *state: pointer to Keccak state -+**************************************************/ -+static void shake256_finalize(keccak_state *state) -+{ -+ keccak_finalize(state->s, state->pos, SHAKE256_RATE, 0x1F); -+ state->pos = SHAKE256_RATE; -+} -+ -+/************************************************* -+* Name: keccak_init -+* -+* Description: Initializes the Keccak state. -+* -+* Arguments: - uint64_t *s: pointer to Keccak state -+**************************************************/ -+static void keccak_init(uint64_t s[25]) -+{ -+ unsigned int i; -+ for(i=0;i<25;i++) -+ s[i] = 0; -+} -+ -+/************************************************* -+* Name: shake256_init -+* -+* Description: Initilizes Keccak state for use as SHAKE256 XOF -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) Keccak state -+**************************************************/ -+static void shake256_init(keccak_state *state) -+{ -+ keccak_init(state->s); -+ state->pos = 0; -+} -+ -+ -+/************************************************* -+* Name: shake256 -+* -+* Description: SHAKE256 XOF with non-incremental API -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: requested output length in bytes -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void shake256(uint8_t *out, size_t outlen, const uint8_t *in, size_t inlen) -+{ -+ size_t nblocks; -+ keccak_state state; -+ -+ shake256_absorb_once(&state, in, inlen); -+ nblocks = outlen/SHAKE256_RATE; -+ shake256_squeezeblocks(out, nblocks, &state); -+ outlen -= nblocks*SHAKE256_RATE; -+ out += nblocks*SHAKE256_RATE; -+ shake256_squeeze(out, outlen, &state); -+} -+ -+/************************************************* -+* Name: sha3_256 -+* -+* Description: SHA3-256 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (32 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_256(uint8_t h[32], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_256_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<4;i++) -+ store64(h+8*i,s[i]); -+} -+ -+/************************************************* -+* Name: sha3_512 -+* -+* Description: SHA3-512 with non-incremental API -+* -+* Arguments: - uint8_t *h: pointer to output (64 bytes) -+* - const uint8_t *in: pointer to input -+* - size_t inlen: length of input in bytes -+**************************************************/ -+static void sha3_512(uint8_t h[64], const uint8_t *in, size_t inlen) -+{ -+ unsigned int i; -+ uint64_t s[25]; -+ -+ keccak_absorb_once(s, SHA3_512_RATE, in, inlen, 0x06); -+ KeccakF1600_StatePermute(s); -+ for(i=0;i<8;i++) -+ store64(h+8*i,s[i]); -+} -+ -+// -+// symmetric-shake.c -+// -+ -+/************************************************* -+* Name: kyber_shake128_absorb -+* -+* Description: Absorb step of the SHAKE128 specialized for the Kyber context. -+* -+* Arguments: - keccak_state *state: pointer to (uninitialized) output Keccak state -+* - const uint8_t *seed: pointer to KYBER_SYMBYTES input to be absorbed into state -+* - uint8_t i: additional byte of input -+* - uint8_t j: additional byte of input -+**************************************************/ -+static void kyber_shake128_absorb(keccak_state *state, -+ const uint8_t seed[KYBER_SYMBYTES], -+ uint8_t x, -+ uint8_t y) -+{ -+ uint8_t extseed[KYBER_SYMBYTES+2]; -+ -+ memcpy(extseed, seed, KYBER_SYMBYTES); -+ extseed[KYBER_SYMBYTES+0] = x; -+ extseed[KYBER_SYMBYTES+1] = y; -+ -+ shake128_absorb_once(state, extseed, sizeof(extseed)); - } - +-} +- -int KYBER_parse_public_key(struct KYBER_public_key *public_key, CBS *in) { - struct public_key *pub = public_key_from_external(public_key); - CBS orig_in = *in; - if (!kyber_parse_public_key_no_hash(pub, in) || // - CBS_len(in) != 0) { -+/************************************************* -+* Name: kyber_shake256_prf -+* -+* Description: Usage of SHAKE256 as a PRF, concatenates secret and public input -+* and then generates outlen bytes of SHAKE256 output -+* -+* Arguments: - uint8_t *out: pointer to output -+* - size_t outlen: number of requested output bytes -+* - const uint8_t *key: pointer to the key (of length KYBER_SYMBYTES) -+* - uint8_t nonce: single-byte nonce (public PRF input) -+**************************************************/ -+static void kyber_shake256_prf(uint8_t *out, size_t outlen, const uint8_t key[KYBER_SYMBYTES], uint8_t nonce) -+{ -+ uint8_t extkey[KYBER_SYMBYTES+1]; -+ -+ memcpy(extkey, key, KYBER_SYMBYTES); -+ extkey[KYBER_SYMBYTES] = nonce; -+ -+ shake256(out, outlen, extkey, sizeof(extkey)); -+} -+ -+// -+// kem.c -+// -+ -+// Modified crypto_kem_keypair to BoringSSL style API -+void generate_key(struct public_key *out_pub, struct private_key *out_priv, -+ const uint8_t seed[KYBER_GENERATE_KEY_BYTES]) -+{ -+ size_t i; -+ uint8_t* pk = &out_pub->opaque[0]; -+ uint8_t* sk = &out_priv->opaque[0]; -+ -+ indcpa_keypair(pk, sk, seed); -+ for(i=0;iopaque[0]; -+ uint8_t *ct = out_ciphertext; -+ -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ -+ memcpy(buf, seed, KYBER_SYMBYTES); -+ -+ /* Don't release system RNG output */ -+ hash_h(buf, buf, KYBER_SYMBYTES); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ hash_h(buf+KYBER_SYMBYTES, pk, KYBER_PUBLICKEYBYTES); -+ hash_g(kr, buf, 2*KYBER_SYMBYTES); -+ -+ /* coins are in kr+KYBER_SYMBYTES */ -+ if(!indcpa_enc(ct, buf, pk, kr+KYBER_SYMBYTES)) - return 0; -+ -+ if (mlkem == 1) { -+ memcpy(ss, kr, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, KYBER_CIPHERTEXTBYTES); -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } +- return 0; +- } - BORINGSSL_keccak(pub->public_key_hash, sizeof(pub->public_key_hash), - CBS_data(&orig_in), CBS_len(&orig_in), boringssl_sha3_256); - return 1; - } - +- return 1; +-} +- -int KYBER_marshal_private_key(CBB *out, - const struct KYBER_private_key *private_key) { - const struct private_key *const priv = private_key_from_external(private_key); - uint8_t *s_output; - if (!CBB_add_space(out, &s_output, kEncodedVectorSize)) { - return 0; -+// Modified crypto_kem_decap to BoringSSL style API -+void decap(uint8_t out_shared_key[KYBER_SSBYTES], -+ const struct private_key *in_priv, -+ const uint8_t *ct, size_t ciphertext_len, int mlkem) -+{ -+ uint8_t *ss = out_shared_key; -+ const uint8_t *sk = &in_priv->opaque[0]; -+ -+ size_t i; -+ int fail = 1; -+ uint8_t buf[2*KYBER_SYMBYTES]; -+ /* Will contain key, coins */ -+ uint8_t kr[2*KYBER_SYMBYTES]; -+ uint8_t cmp[KYBER_CIPHERTEXTBYTES]; -+ const uint8_t *pk = sk+KYBER_INDCPA_SECRETKEYBYTES; -+ -+ if (ciphertext_len == KYBER_CIPHERTEXTBYTES) { -+ indcpa_dec(buf, ct, sk); -+ -+ /* Multitarget countermeasure for coins + contributory KEM */ -+ for(i=0;is, kLog2Prime); - if (!kyber_marshal_public_key(out, &priv->pub) || - !CBB_add_bytes(out, priv->pub.public_key_hash, @@ -3587,45 +3312,14 @@ index 776c085f9..ccb5b3d9b 100644 - !CBB_add_bytes(out, priv->fo_failure_secret, - sizeof(priv->fo_failure_secret))) { - return 0; -+ -+ if (mlkem == 1) { -+ /* Compute shared secret in case of rejection: ss2 = PRF(z || c). */ -+ uint8_t ss2[KYBER_SYMBYTES]; -+ keccak_state ks; -+ shake256_init(&ks); -+ shake256_absorb( -+ &ks, -+ sk + KYBER_SECRETKEYBYTES - KYBER_SYMBYTES, -+ KYBER_SYMBYTES -+ ); -+ shake256_absorb(&ks, ct, ciphertext_len); -+ shake256_finalize(&ks); -+ shake256_squeeze(ss2, KYBER_SYMBYTES, &ks); -+ -+ /* Set ss2 to the real shared secret if c = c' */ -+ cmov(ss2, kr, KYBER_SYMBYTES, 1-fail); -+ memcpy(ss, ss2, KYBER_SYMBYTES); -+ } else { -+ /* overwrite coins in kr with H(c) */ -+ hash_h(kr+KYBER_SYMBYTES, ct, ciphertext_len); -+ -+ /* Overwrite pre-k with z on re-encryption failure */ -+ cmov(kr, sk+KYBER_SECRETKEYBYTES-KYBER_SYMBYTES, KYBER_SYMBYTES, fail); -+ -+ /* hash concatenation of pre-k and H(c) to k */ -+ kdf(ss, kr, 2*KYBER_SYMBYTES); - } +- } - return 1; - } - +-} +- -int KYBER_parse_private_key(struct KYBER_private_key *out_private_key, - CBS *in) { - struct private_key *const priv = private_key_from_external(out_private_key); -+void marshal_public_key(uint8_t out[KYBER_PUBLICKEYBYTES], -+ const struct public_key *in_pub) { -+ memcpy(out, &in_pub->opaque, KYBER_PUBLICKEYBYTES); -+} - +- - CBS s_bytes; - if (!CBS_get_bytes(in, &s_bytes, kEncodedVectorSize) || - !vector_decode(&priv->s, CBS_data(&s_bytes), kLog2Prime) || @@ -3642,33 +3336,33 @@ index 776c085f9..ccb5b3d9b 100644 + const uint8_t in[KYBER_PUBLICKEYBYTES]) { + memcpy(&out->opaque, in, KYBER_PUBLICKEYBYTES); } -diff --git a/src/crypto/kyber/kyber512.c b/src/crypto/kyber/kyber512.c +diff --git a/crypto/kyber/kyber512.c b/crypto/kyber/kyber512.c new file mode 100644 index 000000000..21eed11a2 --- /dev/null -+++ b/src/crypto/kyber/kyber512.c ++++ b/crypto/kyber/kyber512.c @@ -0,0 +1,5 @@ +#define KYBER_K 2 + +#include "kyber.c" + + -diff --git a/src/crypto/kyber/kyber768.c b/src/crypto/kyber/kyber768.c +diff --git a/crypto/kyber/kyber768.c b/crypto/kyber/kyber768.c new file mode 100644 index 000000000..3e572b72e --- /dev/null -+++ b/src/crypto/kyber/kyber768.c ++++ b/crypto/kyber/kyber768.c @@ -0,0 +1,4 @@ +#define KYBER_K 3 + +#include "kyber.c" + -diff --git a/src/crypto/kyber/kyber_test.cc b/src/crypto/kyber/kyber_test.cc +diff --git a/crypto/kyber/kyber_test.cc b/crypto/kyber/kyber_test.cc deleted file mode 100644 -index eb76b5bd7..000000000 ---- a/src/crypto/kyber/kyber_test.cc +index b9daa87d3..000000000 +--- a/crypto/kyber/kyber_test.cc +++ /dev/null -@@ -1,229 +0,0 @@ +@@ -1,184 +0,0 @@ -/* Copyright (c) 2023, Google Inc. - * - * Permission to use, copy, modify, and/or distribute this software for any @@ -3695,55 +3389,10 @@ index eb76b5bd7..000000000 - -#include "../test/file_test.h" -#include "../test/test_util.h" +-#include "../keccak/internal.h" -#include "./internal.h" - - --static void KeccakFileTest(FileTest *t) { -- std::vector input, sha3_256_expected, sha3_512_expected, -- shake128_expected, shake256_expected; -- ASSERT_TRUE(t->GetBytes(&input, "Input")); -- ASSERT_TRUE(t->GetBytes(&sha3_256_expected, "SHA3-256")); -- ASSERT_TRUE(t->GetBytes(&sha3_512_expected, "SHA3-512")); -- ASSERT_TRUE(t->GetBytes(&shake128_expected, "SHAKE-128")); -- ASSERT_TRUE(t->GetBytes(&shake256_expected, "SHAKE-256")); -- -- uint8_t sha3_256_digest[32]; -- BORINGSSL_keccak(sha3_256_digest, sizeof(sha3_256_digest), input.data(), -- input.size(), boringssl_sha3_256); -- uint8_t sha3_512_digest[64]; -- BORINGSSL_keccak(sha3_512_digest, sizeof(sha3_512_digest), input.data(), -- input.size(), boringssl_sha3_512); -- uint8_t shake128_output[512]; -- BORINGSSL_keccak(shake128_output, sizeof(shake128_output), input.data(), -- input.size(), boringssl_shake128); -- uint8_t shake256_output[512]; -- BORINGSSL_keccak(shake256_output, sizeof(shake256_output), input.data(), -- input.size(), boringssl_shake256); -- -- EXPECT_EQ(Bytes(sha3_256_expected), Bytes(sha3_256_digest)); -- EXPECT_EQ(Bytes(sha3_512_expected), Bytes(sha3_512_digest)); -- EXPECT_EQ(Bytes(shake128_expected), Bytes(shake128_output)); -- EXPECT_EQ(Bytes(shake256_expected), Bytes(shake256_output)); -- -- struct BORINGSSL_keccak_st ctx; -- -- BORINGSSL_keccak_init(&ctx, input.data(), input.size(), boringssl_shake128); -- for (size_t i = 0; i < sizeof(shake128_output); i++) { -- BORINGSSL_keccak_squeeze(&ctx, &shake128_output[i], 1); -- } -- EXPECT_EQ(Bytes(shake128_expected), Bytes(shake128_output)); -- -- BORINGSSL_keccak_init(&ctx, input.data(), input.size(), boringssl_shake256); -- for (size_t i = 0; i < sizeof(shake256_output); i++) { -- BORINGSSL_keccak_squeeze(&ctx, &shake256_output[i], 1); -- } -- EXPECT_EQ(Bytes(shake256_expected), Bytes(shake256_output)); --} -- --TEST(KyberTest, KeccakTestVectors) { -- FileTestGTest("crypto/kyber/keccak_tests.txt", KeccakFileTest); --} -- -template -static std::vector Marshal(int (*marshal_func)(CBB *, const T *), - const T *t) { @@ -3898,10 +3547,10 @@ index eb76b5bd7..000000000 -TEST(KyberTest, TestVectors) { - FileTestGTest("crypto/kyber/kyber_tests.txt", KyberFileTest); -} -diff --git a/src/crypto/obj/obj_dat.h b/src/crypto/obj/obj_dat.h -index 654b3c08e..6cef2c079 100644 ---- a/src/crypto/obj/obj_dat.h -+++ b/src/crypto/obj/obj_dat.h +diff --git a/crypto/obj/obj_dat.h b/crypto/obj/obj_dat.h +index 71ef2d2bd..74b99b098 100644 +--- a/crypto/obj/obj_dat.h ++++ b/crypto/obj/obj_dat.h @@ -57,7 +57,7 @@ /* This file is generated by crypto/obj/objects.go. */ @@ -3911,7 +3560,7 @@ index 654b3c08e..6cef2c079 100644 static const uint8_t kObjectData[] = { /* NID_rsadsi */ -@@ -8784,6 +8784,13 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { +@@ -8783,6 +8783,13 @@ static const ASN1_OBJECT kObjects[NUM_NID] = { {"HKDF", "hkdf", NID_hkdf, 0, NULL, 0}, {"X25519Kyber768Draft00", "X25519Kyber768Draft00", NID_X25519Kyber768Draft00, 0, NULL, 0}, @@ -3925,7 +3574,7 @@ index 654b3c08e..6cef2c079 100644 }; static const uint16_t kNIDsInShortNameOrder[] = { -@@ -8916,6 +8923,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { +@@ -8915,6 +8922,7 @@ static const uint16_t kNIDsInShortNameOrder[] = { 18 /* OU */, 749 /* Oakley-EC2N-3 */, 750 /* Oakley-EC2N-4 */, @@ -3933,9 +3582,9 @@ index 654b3c08e..6cef2c079 100644 9 /* PBE-MD2-DES */, 168 /* PBE-MD2-RC2-64 */, 10 /* PBE-MD5-DES */, -@@ -8982,7 +8990,10 @@ static const uint16_t kNIDsInShortNameOrder[] = { +@@ -8980,7 +8988,10 @@ static const uint16_t kNIDsInShortNameOrder[] = { + 143 /* SXNetID */, 458 /* UID */, - 0 /* UNDEF */, 948 /* X25519 */, + 965 /* X25519Kyber512Draft00 */, 964 /* X25519Kyber768Draft00 */, @@ -3944,7 +3593,7 @@ index 654b3c08e..6cef2c079 100644 961 /* X448 */, 11 /* X500 */, 378 /* X500algorithms */, -@@ -9829,6 +9840,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { +@@ -9827,6 +9838,7 @@ static const uint16_t kNIDsInLongNameOrder[] = { 366 /* OCSP Nonce */, 371 /* OCSP Service Locator */, 180 /* OCSP Signing */, @@ -3952,7 +3601,7 @@ index 654b3c08e..6cef2c079 100644 161 /* PBES2 */, 69 /* PBKDF2 */, 162 /* PBMAC1 */, -@@ -9853,7 +9865,10 @@ static const uint16_t kNIDsInLongNameOrder[] = { +@@ -9851,7 +9863,10 @@ static const uint16_t kNIDsInLongNameOrder[] = { 133 /* Time Stamping */, 375 /* Trust Root */, 948 /* X25519 */, @@ -3963,10 +3612,10 @@ index 654b3c08e..6cef2c079 100644 961 /* X448 */, 12 /* X509 */, 402 /* X509v3 AC Targeting */, -diff --git a/src/crypto/obj/obj_mac.num b/src/crypto/obj/obj_mac.num +diff --git a/crypto/obj/obj_mac.num b/crypto/obj/obj_mac.num index a0519acee..2a46adfe8 100644 ---- a/src/crypto/obj/obj_mac.num -+++ b/src/crypto/obj/obj_mac.num +--- a/crypto/obj/obj_mac.num ++++ b/crypto/obj/obj_mac.num @@ -952,3 +952,7 @@ X448 961 sha512_256 962 hkdf 963 @@ -3975,10 +3624,10 @@ index a0519acee..2a46adfe8 100644 +P256Kyber768Draft00 966 +X25519Kyber768Draft00Old 967 +X25519MLKEM768 968 -diff --git a/src/crypto/obj/objects.txt b/src/crypto/obj/objects.txt +diff --git a/crypto/obj/objects.txt b/crypto/obj/objects.txt index 3ad32ea3d..347fc556a 100644 ---- a/src/crypto/obj/objects.txt -+++ b/src/crypto/obj/objects.txt +--- a/crypto/obj/objects.txt ++++ b/crypto/obj/objects.txt @@ -1332,8 +1332,12 @@ secg-scheme 14 3 : dhSinglePass-cofactorDH-sha512kdf-scheme : dh-std-kdf : dh-cofactor-kdf @@ -3993,10 +3642,10 @@ index 3ad32ea3d..347fc556a 100644 # See RFC 8410. 1 3 101 110 : X25519 -diff --git a/src/include/openssl/kyber.h b/src/include/openssl/kyber.h +diff --git a/include/openssl/kyber.h b/include/openssl/kyber.h index cafae9d17..a05eb8957 100644 ---- a/src/include/openssl/kyber.h -+++ b/src/include/openssl/kyber.h +--- a/include/openssl/kyber.h ++++ b/include/openssl/kyber.h @@ -1,17 +1,3 @@ -/* Copyright (c) 2023, Google Inc. - * @@ -4038,15 +3687,7 @@ index cafae9d17..a05eb8957 100644 - } opaque; +struct KYBER512_private_key { + uint8_t opaque[KYBER512_PRIVATE_KEY_BYTES]; - }; -- --// KYBER_private_key contains a Kyber768 private key. The contents of this --// object should never leave the address space since the format is unstable. --struct KYBER_private_key { -- union { -- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; -- uint16_t alignment; -- } opaque; ++}; +struct KYBER768_private_key { + uint8_t opaque[KYBER768_PRIVATE_KEY_BYTES]; +}; @@ -4057,17 +3698,34 @@ index cafae9d17..a05eb8957 100644 + uint8_t opaque[KYBER768_PUBLIC_KEY_BYTES]; }; +-// KYBER_private_key contains a Kyber768 private key. The contents of this +-// object should never leave the address space since the format is unstable. +-struct KYBER_private_key { +- union { +- uint8_t bytes[512 * (3 + 3 + 9) + 32 + 32 + 32]; +- uint16_t alignment; +- } opaque; +-}; ++// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to ++// generate a keypair. ++#define KYBER_GENERATE_KEY_BYTES 64 + -// KYBER_PUBLIC_KEY_BYTES is the number of bytes in an encoded Kyber768 public -// key. -#define KYBER_PUBLIC_KEY_BYTES 1184 -- ++// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a ++// session key. ++#define KYBER_ENCAP_BYTES 32 + -// KYBER_generate_key generates a random public/private key pair, writes the -// encoded public key to |out_encoded_public_key| and sets |out_private_key| to -// the private key. -OPENSSL_EXPORT void KYBER_generate_key( - uint8_t out_encoded_public_key[KYBER_PUBLIC_KEY_BYTES], - struct KYBER_private_key *out_private_key); -- ++// KYBER_KEY_BYTES is the number of bytes in a shared key. ++#define KYBER_KEY_BYTES 32 + -// KYBER_public_from_private sets |*out_public_key| to the public key that -// corresponds to |private_key|. (This is faster than parsing the output of -// |KYBER_generate_key| if, for some reason, you need to encapsulate to a key @@ -4075,10 +3733,20 @@ index cafae9d17..a05eb8957 100644 -OPENSSL_EXPORT void KYBER_public_from_private( - struct KYBER_public_key *out_public_key, - const struct KYBER_private_key *private_key); -- ++// KYBER512_generate_key is a deterministic function that outputs a public and ++// private key based on the given entropy. ++OPENSSL_EXPORT void KYBER512_generate_key( ++ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, ++ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); + -// KYBER_CIPHERTEXT_BYTES is number of bytes in the Kyber768 ciphertext. -#define KYBER_CIPHERTEXT_BYTES 1088 -- ++// KYBER768_generate_key is a deterministic function that outputs a public and ++// private key based on the given entropy. ++OPENSSL_EXPORT void KYBER768_generate_key( ++ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, ++ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); + -// KYBER_encap encrypts a random secret key of length |out_shared_secret_len| to -// |public_key|, writes the ciphertext to |ciphertext|, and writes the random -// key to |out_shared_secret|. The party calling |KYBER_decap| must already know @@ -4087,7 +3755,15 @@ index cafae9d17..a05eb8957 100644 - uint8_t *out_shared_secret, - size_t out_shared_secret_len, - const struct KYBER_public_key *public_key); -- ++// KYBER512_encap is a deterministic function the generates and encrypts a random ++// session key from the given entropy, writing those values to |out_shared_key| ++// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. ++OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], ++ uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER512_public_key *in_pub, ++ const uint8_t in[KYBER_ENCAP_BYTES], ++ int mlkem); + -// KYBER_decap decrypts a key of length |out_shared_secret_len| from -// |ciphertext| using |private_key| and writes it to |out_shared_secret|. If -// |ciphertext| is invalid, |out_shared_secret| is filled with a key that @@ -4100,23 +3776,57 @@ index cafae9d17..a05eb8957 100644 - uint8_t *out_shared_secret, size_t out_shared_secret_len, - const uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES], - const struct KYBER_private_key *private_key); -- -- ++// KYBER768_encap is a deterministic function the generates and encrypts a random ++// session key from the given entropy, writing those values to |out_shared_key| ++// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. ++OPENSSL_EXPORT int KYBER768_encap(uint8_t out_ciphertext[KYBER768_CIPHERTEXT_BYTES], ++ uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER768_public_key *in_pub, ++ const uint8_t in[KYBER_ENCAP_BYTES], ++ int mlkem); + ++// KYBER_decap decrypts a session key from |ciphertext_len| bytes of ++// |ciphertext|. If the ciphertext is valid, the decrypted key is written to ++// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept ++// in |in_priv|) is written. If the ciphertext is the wrong length then it will ++// leak which was done via side-channels. Otherwise it should perform either ++// action in constant-time. If |mlkem| is 1, will use ML-KEM-512. ++OPENSSL_EXPORT void KYBER512_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER512_private_key *in_priv, ++ const uint8_t *ciphertext, size_t ciphertext_len, ++ int mlkem); + -// Serialisation of keys. -- ++// KYBER_decap decrypts a session key from |ciphertext_len| bytes of ++// |ciphertext|. If the ciphertext is valid, the decrypted key is written to ++// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept ++// in |in_priv|) is written. If the ciphertext is the wrong length then it will ++// leak which was done via side-channels. Otherwise it should perform either ++// action in constant-time. If |mlkem| is 1, will use ML-KEM-768. ++OPENSSL_EXPORT void KYBER768_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], ++ const struct KYBER768_private_key *in_priv, ++ const uint8_t *ciphertext, size_t ciphertext_len, ++ int mlkem); + -// KYBER_marshal_public_key serializes |public_key| to |out| in the standard -// format for Kyber public keys. It returns one on success or zero on allocation -// error. -OPENSSL_EXPORT int KYBER_marshal_public_key( - CBB *out, const struct KYBER_public_key *public_key); -- ++// KYBER512_marshal_public_key serialises |in_pub| to |out|. ++OPENSSL_EXPORT void KYBER512_marshal_public_key( ++ uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); + -// KYBER_parse_public_key parses a public key, in the format generated by -// |KYBER_marshal_public_key|, from |in| and writes the result to -// |out_public_key|. It returns one on success or zero on parse error or if -// there are trailing bytes in |in|. -OPENSSL_EXPORT int KYBER_parse_public_key( - struct KYBER_public_key *out_public_key, CBS *in); -- ++// KYBER768_marshal_public_key serialises |in_pub| to |out|. ++OPENSSL_EXPORT void KYBER768_marshal_public_key( ++ uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); + -// KYBER_marshal_private_key serializes |private_key| to |out| in the standard -// format for Kyber private keys. It returns one on success or zero on -// allocation error. @@ -4133,92 +3843,20 @@ index cafae9d17..a05eb8957 100644 -// there are trailing bytes in |in|. -OPENSSL_EXPORT int KYBER_parse_private_key( - struct KYBER_private_key *out_private_key, CBS *in); -- -+// KYBER_GENERATE_KEY_BYTES is the number of bytes of entropy needed to -+// generate a keypair. -+#define KYBER_GENERATE_KEY_BYTES 64 -+ -+// KYBER_ENCAP_BYTES is the number of bytes of entropy needed to encapsulate a -+// session key. -+#define KYBER_ENCAP_BYTES 32 -+ -+// KYBER_KEY_BYTES is the number of bytes in a shared key. -+#define KYBER_KEY_BYTES 32 -+ -+// KYBER512_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER512_generate_key( -+ struct KYBER512_public_key *out_pub, struct KYBER512_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER768_generate_key is a deterministic function that outputs a public and -+// private key based on the given entropy. -+OPENSSL_EXPORT void KYBER768_generate_key( -+ struct KYBER768_public_key *out_pub, struct KYBER768_private_key *out_priv, -+ const uint8_t input[KYBER_GENERATE_KEY_BYTES]); -+ -+// KYBER512_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT int KYBER512_encap(uint8_t out_ciphertext[KYBER512_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER768_encap is a deterministic function the generates and encrypts a random -+// session key from the given entropy, writing those values to |out_shared_key| -+// and |out_ciphertext|, respectively. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT int KYBER768_encap(uint8_t out_ciphertext[KYBER768_CIPHERTEXT_BYTES], -+ uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_public_key *in_pub, -+ const uint8_t in[KYBER_ENCAP_BYTES], -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-512. -+OPENSSL_EXPORT void KYBER512_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER512_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER_decap decrypts a session key from |ciphertext_len| bytes of -+// |ciphertext|. If the ciphertext is valid, the decrypted key is written to -+// |out_shared_key|. Otherwise a key dervied from |ciphertext| and a secret key (kept -+// in |in_priv|) is written. If the ciphertext is the wrong length then it will -+// leak which was done via side-channels. Otherwise it should perform either -+// action in constant-time. If |mlkem| is 1, will use ML-KEM-768. -+OPENSSL_EXPORT void KYBER768_decap(uint8_t out_shared_key[KYBER_KEY_BYTES], -+ const struct KYBER768_private_key *in_priv, -+ const uint8_t *ciphertext, size_t ciphertext_len, -+ int mlkem); -+ -+// KYBER512_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER512_marshal_public_key( -+ uint8_t out[KYBER512_PUBLIC_KEY_BYTES], const struct KYBER512_public_key *in_pub); -+ -+// KYBER768_marshal_public_key serialises |in_pub| to |out|. -+OPENSSL_EXPORT void KYBER768_marshal_public_key( -+ uint8_t out[KYBER768_PUBLIC_KEY_BYTES], const struct KYBER768_public_key *in_pub); -+ +// KYBER512_parse_public_key sets |*out| to the public-key encoded in |in|. +OPENSSL_EXPORT void KYBER512_parse_public_key( + struct KYBER512_public_key *out, const uint8_t in[KYBER512_PUBLIC_KEY_BYTES]); -+ + +// KYBER768_parse_public_key sets |*out| to the public-key encoded in |in|. +OPENSSL_EXPORT void KYBER768_parse_public_key( + struct KYBER768_public_key *out, const uint8_t in[KYBER768_PUBLIC_KEY_BYTES]); #if defined(__cplusplus) } // extern C -diff --git a/src/include/openssl/nid.h b/src/include/openssl/nid.h +diff --git a/include/openssl/nid.h b/include/openssl/nid.h index 4dd8841b1..5b102c610 100644 ---- a/src/include/openssl/nid.h -+++ b/src/include/openssl/nid.h +--- a/include/openssl/nid.h ++++ b/include/openssl/nid.h @@ -4255,6 +4255,18 @@ extern "C" { #define SN_X25519Kyber768Draft00 "X25519Kyber768Draft00" #define NID_X25519Kyber768Draft00 964 @@ -4238,53 +3876,60 @@ index 4dd8841b1..5b102c610 100644 #if defined(__cplusplus) } /* extern C */ -diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h -index 53aa9b453..f9683f4cf 100644 ---- a/src/include/openssl/ssl.h -+++ b/src/include/openssl/ssl.h -@@ -2378,6 +2378,10 @@ OPENSSL_EXPORT int SSL_set1_curves_list(SSL *ssl, const char *curves); - #define SSL_CURVE_SECP521R1 25 - #define SSL_CURVE_X25519 29 - #define SSL_CURVE_X25519_KYBER768_DRAFT00 0x6399 -+#define SSL_CURVE_X25519_KYBER512_DRAFT00 0xfe30 -+#define SSL_CURVE_X25519_KYBER768_DRAFT00_OLD 0xfe31 -+#define SSL_CURVE_P256_KYBER768_DRAFT00 0xfe32 -+#define SSL_CURVE_X25519_MLKEM768 0x11ec +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index 003e0a5f7..884685ba9 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h +@@ -2363,6 +2363,10 @@ OPENSSL_EXPORT size_t SSL_CTX_get_num_tickets(const SSL_CTX *ctx); + #define SSL_GROUP_SECP521R1 25 + #define SSL_GROUP_X25519 29 + #define SSL_GROUP_X25519_KYBER768_DRAFT00 0x6399 ++#define SSL_GROUP_X25519_KYBER512_DRAFT00 0xfe30 ++#define SSL_GROUP_X25519_KYBER768_DRAFT00_OLD 0xfe31 ++#define SSL_GROUP_P256_KYBER768_DRAFT00 0xfe32 ++#define SSL_GROUP_X25519_MLKEM768 0x11ec - // SSL_get_curve_id returns the ID of the curve used by |ssl|'s most recently - // completed handshake or 0 if not applicable. -diff --git a/src/sources.cmake b/src/sources.cmake -index 5c7e881bf..3c0770cf3 100644 ---- a/src/sources.cmake -+++ b/src/sources.cmake -@@ -66,8 +66,6 @@ set( - crypto/fipsmodule/rand/ctrdrbg_vectors.txt + // SSL_CTX_set1_group_ids sets the preferred groups for |ctx| to |group_ids|. + // Each element of |group_ids| should be one of the |SSL_GROUP_*| constants. It +diff --git a/sources.cmake b/sources.cmake +index ba2f5bc9e..d7ef5153a 100644 +--- a/sources.cmake ++++ b/sources.cmake +@@ -52,7 +52,6 @@ set( + crypto/hrss/hrss_test.cc + crypto/impl_dispatch_test.cc + crypto/keccak/keccak_test.cc +- crypto/kyber/kyber_test.cc + crypto/lhash/lhash_test.cc + crypto/obj/obj_test.cc + crypto/pem/pem_test.cc +@@ -145,7 +144,6 @@ set( crypto/hmac_extra/hmac_tests.txt crypto/hpke/hpke_test_vectors.txt -- crypto/kyber/keccak_tests.txt + crypto/keccak/keccak_tests.txt - crypto/kyber/kyber_tests.txt crypto/pkcs8/test/empty_password.p12 crypto/pkcs8/test/no_encryption.p12 crypto/pkcs8/test/nss.p12 -diff --git a/src/ssl/extensions.cc b/src/ssl/extensions.cc -index 5ee280221..aae3e6a7f 100644 ---- a/src/ssl/extensions.cc -+++ b/src/ssl/extensions.cc +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index b13400097..4655b1881 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc @@ -207,6 +207,10 @@ static bool tls1_check_duplicate_extensions(const CBS *cbs) { static bool is_post_quantum_group(uint16_t id) { switch (id) { - case SSL_CURVE_X25519_KYBER768_DRAFT00: -+ case SSL_CURVE_X25519_KYBER768_DRAFT00_OLD: -+ case SSL_CURVE_X25519_KYBER512_DRAFT00: -+ case SSL_CURVE_P256_KYBER768_DRAFT00: -+ case SSL_CURVE_X25519_MLKEM768: + case SSL_GROUP_X25519_KYBER768_DRAFT00: ++ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: ++ case SSL_GROUP_X25519_KYBER512_DRAFT00: ++ case SSL_GROUP_P256_KYBER768_DRAFT00: ++ case SSL_GROUP_X25519_MLKEM768: return true; default: return false; -diff --git a/src/ssl/ssl_key_share.cc b/src/ssl/ssl_key_share.cc -index 09a9ad380..d7a8f0a80 100644 ---- a/src/ssl/ssl_key_share.cc -+++ b/src/ssl/ssl_key_share.cc +diff --git a/ssl/ssl_key_share.cc b/ssl/ssl_key_share.cc +index 694bec11d..3e4d2e7c4 100644 +--- a/ssl/ssl_key_share.cc ++++ b/ssl/ssl_key_share.cc @@ -26,6 +26,7 @@ #include #include @@ -4293,7 +3938,7 @@ index 09a9ad380..d7a8f0a80 100644 #include #include #include -@@ -193,63 +194,292 @@ class X25519KeyShare : public SSLKeyShare { +@@ -191,63 +192,145 @@ class X25519KeyShare : public SSLKeyShare { uint8_t private_key_[32]; }; @@ -4302,18 +3947,27 @@ index 09a9ad380..d7a8f0a80 100644 public: - X25519Kyber768KeyShare() {} + P256Kyber768Draft00KeyShare() {} -+ -+ uint16_t GroupID() const override { return SSL_CURVE_P256_KYBER768_DRAFT00; } -+ -+ bool Generate(CBB *out) override { + +- uint16_t GroupID() const override { +- return SSL_GROUP_X25519_KYBER768_DRAFT00; +- } ++ uint16_t GroupID() const override { return SSL_GROUP_P256_KYBER768_DRAFT00; } + + bool Generate(CBB *out) override { +- uint8_t x25519_public_key[32]; +- X25519_keypair(x25519_public_key, x25519_private_key_); + assert(!p256_private_key_); -+ + +- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; +- KYBER_generate_key(kyber_public_key, &kyber_private_key_); + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { + return false; + } -+ + +- if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || +- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { + BN_CTXScope scope(bn_ctx.get()); + + // Generate a P-256 private key. @@ -4345,33 +3999,58 @@ index 09a9ad380..d7a8f0a80 100644 + + uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; + KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - -- uint16_t GroupID() const override { -- return SSL_CURVE_X25519_KYBER768_DRAFT00; ++ + if (!CBB_add_bytes(out, kyber_public_key_bytes, + sizeof(kyber_public_key_bytes))) { -+ return false; -+ } -+ -+ return true; + return false; + } + + return true; } +- bool Encap(CBB *out_ciphertext, Array *out_secret, +- uint8_t *out_alert, Span peer_key) override { +- Array secret; +- if (!secret.Init(32 + 32)) { +- return false; +- } + bool Encap(CBB *out_public_key, Array *out_secret, + uint8_t *out_alert, Span peer_key) override { + assert(!p256_private_key_); -+ + +- uint8_t x25519_public_key[32]; +- X25519_keypair(x25519_public_key, x25519_private_key_); +- KYBER_public_key peer_kyber_pub; +- CBS peer_key_cbs; +- CBS peer_x25519_cbs; +- CBS peer_kyber_cbs; +- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); +- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || +- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, +- KYBER_PUBLIC_KEY_BYTES) || +- CBS_len(&peer_key_cbs) != 0 || +- !X25519(secret.data(), x25519_private_key_, +- CBS_data(&peer_x25519_cbs)) || +- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { + if (peer_key.size() != 65 + KYBER768_PUBLIC_KEY_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + +- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; +- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, +- &peer_kyber_pub); + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { + return false; + } -+ + +- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, +- sizeof(x25519_public_key)) || +- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, +- sizeof(kyber_ciphertext))) { + BN_CTXScope scope(bn_ctx.get()); + + UniquePtr group; @@ -4440,30 +4119,35 @@ index 09a9ad380..d7a8f0a80 100644 + return false; + } + if(!CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { -+ return false; -+ } -+ -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ bool Decap(Array *out_secret, uint8_t *out_alert, + return false; + } + +@@ -256,30 +339,380 @@ class X25519Kyber768KeyShare : public SSLKeyShare { + } + + bool Decap(Array *out_secret, uint8_t *out_alert, +- Span ciphertext) override { + Span peer_key) override { + assert(p256_private_key_); -+ *out_alert = SSL_AD_INTERNAL_ERROR; -+ -+ Array secret; + *out_alert = SSL_AD_INTERNAL_ERROR; + + Array secret; +- if (!secret.Init(32 + 32)) { + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); -+ return false; -+ } -+ + return false; + } + +- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || +- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { + if (peer_key.size() != 65 + KYBER768_CIPHERTEXT_BYTES) { -+ *out_alert = SSL_AD_DECODE_ERROR; -+ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); -+ return false; -+ } -+ + *out_alert = SSL_AD_DECODE_ERROR; + OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); + return false; + } + +- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, +- &kyber_private_key_); + // Set up a shared |BN_CTX| for P-256 operations. + UniquePtr bn_ctx(BN_CTX_new()); + if (!bn_ctx) { @@ -4523,18 +4207,16 @@ index 09a9ad380..d7a8f0a80 100644 +class X25519Kyber768Draft00KeyShare : public SSLKeyShare { + public: + X25519Kyber768Draft00KeyShare(uint16_t group_id) : group_id_(group_id) { -+ assert(group_id == SSL_CURVE_X25519_KYBER768_DRAFT00 -+ || group_id == SSL_CURVE_X25519_KYBER768_DRAFT00_OLD); ++ assert(group_id == SSL_GROUP_X25519_KYBER768_DRAFT00 ++ || group_id == SSL_GROUP_X25519_KYBER768_DRAFT00_OLD); + } + + uint16_t GroupID() const override { return group_id_; } + - bool Generate(CBB *out) override { - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); - -- uint8_t kyber_public_key[KYBER_PUBLIC_KEY_BYTES]; -- KYBER_generate_key(kyber_public_key, &kyber_private_key_); ++ bool Generate(CBB *out) override { ++ uint8_t x25519_public_key[32]; ++ X25519_keypair(x25519_public_key, x25519_private_key_); ++ + uint8_t kyber_entropy[KYBER_GENERATE_KEY_BYTES]; + KYBER768_public_key kyber_public_key; + RAND_bytes(kyber_entropy, sizeof(kyber_entropy)); @@ -4542,42 +4224,26 @@ index 09a9ad380..d7a8f0a80 100644 + + uint8_t kyber_public_key_bytes[KYBER768_PUBLIC_KEY_BYTES]; + KYBER768_marshal_public_key(kyber_public_key_bytes, &kyber_public_key); - - if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || -- !CBB_add_bytes(out, kyber_public_key, sizeof(kyber_public_key))) { ++ ++ if (!CBB_add_bytes(out, x25519_public_key, sizeof(x25519_public_key)) || + !CBB_add_bytes(out, kyber_public_key_bytes, + sizeof(kyber_public_key_bytes))) { - return false; - } - - return true; - } - -- bool Encap(CBB *out_ciphertext, Array *out_secret, -- uint8_t *out_alert, Span peer_key) override { ++ return false; ++ } ++ ++ return true; ++ } ++ + bool Encap(CBB *out_public_key, Array *out_secret, + uint8_t *out_alert, Span peer_key) override { - Array secret; -- if (!secret.Init(32 + 32)) { ++ Array secret; + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - - uint8_t x25519_public_key[32]; - X25519_keypair(x25519_public_key, x25519_private_key_); -- KYBER_public_key peer_kyber_pub; -- CBS peer_key_cbs; -- CBS peer_x25519_cbs; -- CBS peer_kyber_cbs; -- CBS_init(&peer_key_cbs, peer_key.data(), peer_key.size()); -- if (!CBS_get_bytes(&peer_key_cbs, &peer_x25519_cbs, 32) || -- !CBS_get_bytes(&peer_key_cbs, &peer_kyber_cbs, -- KYBER_PUBLIC_KEY_BYTES) || -- CBS_len(&peer_key_cbs) != 0 || -- !X25519(secret.data(), x25519_private_key_, -- CBS_data(&peer_x25519_cbs)) || -- !KYBER_parse_public_key(&peer_kyber_pub, &peer_kyber_cbs)) { ++ return false; ++ } ++ ++ uint8_t x25519_public_key[32]; ++ X25519_keypair(x25519_public_key, x25519_private_key_); + + KYBER768_public_key peer_public_key; + if (peer_key.size() != 32 + KYBER768_PUBLIC_KEY_BYTES) { @@ -4589,36 +4255,30 @@ index 09a9ad380..d7a8f0a80 100644 + KYBER768_parse_public_key(&peer_public_key, peer_key.data() + 32); + + if (!X25519(secret.data(), x25519_private_key_, peer_key.data())) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- uint8_t kyber_ciphertext[KYBER_CIPHERTEXT_BYTES]; -- KYBER_encap(kyber_ciphertext, secret.data() + 32, secret.size() - 32, -- &peer_kyber_pub); ++ *out_alert = SSL_AD_DECODE_ERROR; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ + uint8_t ciphertext[KYBER768_CIPHERTEXT_BYTES]; + uint8_t entropy[KYBER_ENCAP_BYTES]; + RAND_bytes(entropy, sizeof(entropy)); - -- if (!CBB_add_bytes(out_ciphertext, x25519_public_key, ++ + if(!KYBER768_encap(ciphertext, secret.data() + 32, &peer_public_key, entropy, 0)) { + *out_alert = SSL_AD_ILLEGAL_PARAMETER; + return false; + } + if(!CBB_add_bytes(out_public_key, x25519_public_key, - sizeof(x25519_public_key)) || -- !CBB_add_bytes(out_ciphertext, kyber_ciphertext, -- sizeof(kyber_ciphertext))) { ++ sizeof(x25519_public_key)) || + !CBB_add_bytes(out_public_key, ciphertext, sizeof(ciphertext))) { - return false; - } - -@@ -258,30 +488,233 @@ class X25519Kyber768KeyShare : public SSLKeyShare { - } - - bool Decap(Array *out_secret, uint8_t *out_alert, -- Span ciphertext) override { ++ return false; ++ } ++ ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ bool Decap(Array *out_secret, uint8_t *out_alert, + Span peer_key) override { + *out_alert = SSL_AD_INTERNAL_ERROR; + @@ -4638,12 +4298,13 @@ index 09a9ad380..d7a8f0a80 100644 + KYBER768_decap(secret.data() + 32, &kyber_private_key_, + peer_key.data() + 32, peer_key.size() - 32, 0); + -+ *out_secret = std::move(secret); -+ return true; -+ } -+ -+ private: -+ uint8_t x25519_private_key_[32]; + *out_secret = std::move(secret); + return true; + } + + private: + uint8_t x25519_private_key_[32]; +- KYBER_private_key kyber_private_key_; + KYBER768_private_key kyber_private_key_; + uint16_t group_id_; +}; @@ -4652,7 +4313,7 @@ index 09a9ad380..d7a8f0a80 100644 + public: + X25519MLKEM768KeyShare() {} + -+ uint16_t GroupID() const override { return SSL_CURVE_X25519_MLKEM768; } ++ uint16_t GroupID() const override { return SSL_GROUP_X25519_MLKEM768; } + + bool Generate(CBB *out) override { + uint8_t x25519_public_key[32]; @@ -4720,27 +4381,22 @@ index 09a9ad380..d7a8f0a80 100644 + + bool Decap(Array *out_secret, uint8_t *out_alert, + Span peer_key) override { - *out_alert = SSL_AD_INTERNAL_ERROR; - - Array secret; -- if (!secret.Init(32 + 32)) { ++ *out_alert = SSL_AD_INTERNAL_ERROR; ++ ++ Array secret; + if (!secret.Init(32 + KYBER_KEY_BYTES)) { + OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE); - return false; - } - -- if (ciphertext.size() != 32 + KYBER_CIPHERTEXT_BYTES || -- !X25519(secret.data(), x25519_private_key_, ciphertext.data())) { ++ return false; ++ } ++ + if (peer_key.size() != KYBER768_CIPHERTEXT_BYTES + 32 || + !X25519(secret.data() + 32, x25519_private_key_, + peer_key.data() + KYBER768_CIPHERTEXT_BYTES )) { - *out_alert = SSL_AD_DECODE_ERROR; - OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); - return false; - } - -- KYBER_decap(secret.data() + 32, secret.size() - 32, ciphertext.data() + 32, -- &kyber_private_key_); ++ *out_alert = SSL_AD_DECODE_ERROR; ++ OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_ECPOINT); ++ return false; ++ } ++ + KYBER768_decap(secret.data(), &kyber_private_key_, + peer_key.data(), peer_key.size() - 32, 1); + @@ -4757,7 +4413,7 @@ index 09a9ad380..d7a8f0a80 100644 + public: + X25519Kyber512Draft00KeyShare() {} + -+ uint16_t GroupID() const override { return SSL_CURVE_X25519_KYBER512_DRAFT00; } ++ uint16_t GroupID() const override { return SSL_GROUP_X25519_KYBER512_DRAFT00; } + + bool Generate(CBB *out) override { + uint8_t x25519_public_key[32]; @@ -4844,113 +4500,112 @@ index 09a9ad380..d7a8f0a80 100644 + KYBER512_decap(secret.data() + 32, &kyber_private_key_, + peer_key.data() + 32, peer_key.size() - 32, 0); + - *out_secret = std::move(secret); - return true; - } - - private: - uint8_t x25519_private_key_[32]; -- KYBER_private_key kyber_private_key_; ++ *out_secret = std::move(secret); ++ return true; ++ } ++ ++ private: ++ uint8_t x25519_private_key_[32]; + KYBER512_private_key kyber_private_key_; }; constexpr NamedGroup kNamedGroups[] = { -@@ -290,8 +723,16 @@ constexpr NamedGroup kNamedGroups[] = { - {NID_secp384r1, SSL_CURVE_SECP384R1, "P-384", "secp384r1"}, - {NID_secp521r1, SSL_CURVE_SECP521R1, "P-521", "secp521r1"}, - {NID_X25519, SSL_CURVE_X25519, "X25519", "x25519"}, -+ {NID_X25519Kyber512Draft00, SSL_CURVE_X25519_KYBER512_DRAFT00, +@@ -288,8 +721,16 @@ constexpr NamedGroup kNamedGroups[] = { + {NID_secp384r1, SSL_GROUP_SECP384R1, "P-384", "secp384r1"}, + {NID_secp521r1, SSL_GROUP_SECP521R1, "P-521", "secp521r1"}, + {NID_X25519, SSL_GROUP_X25519, "X25519", "x25519"}, ++ {NID_X25519Kyber512Draft00, SSL_GROUP_X25519_KYBER512_DRAFT00, + "X25519Kyber512Draft00", "Xyber512D00"}, - {NID_X25519Kyber768Draft00, SSL_CURVE_X25519_KYBER768_DRAFT00, + {NID_X25519Kyber768Draft00, SSL_GROUP_X25519_KYBER768_DRAFT00, - "X25519Kyber768Draft00", ""}, + "X25519Kyber768Draft00", "Xyber768D00"}, -+ {NID_X25519Kyber768Draft00Old, SSL_CURVE_X25519_KYBER768_DRAFT00_OLD, ++ {NID_X25519Kyber768Draft00Old, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD, + "X25519Kyber768Draft00Old", "Xyber768D00Old"}, -+ {NID_P256Kyber768Draft00, SSL_CURVE_P256_KYBER768_DRAFT00, ++ {NID_P256Kyber768Draft00, SSL_GROUP_P256_KYBER768_DRAFT00, + "P256Kyber768Draft00", "P256Kyber768D00"}, -+ {NID_X25519MLKEM768, SSL_CURVE_X25519_MLKEM768, ++ {NID_X25519MLKEM768, SSL_GROUP_X25519_MLKEM768, + "X25519MLKEM768", "X25519MLKEM768"} }; } // namespace -@@ -312,8 +753,18 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { - return MakeUnique(NID_secp521r1, SSL_CURVE_SECP521R1); - case SSL_CURVE_X25519: +@@ -310,8 +751,18 @@ UniquePtr SSLKeyShare::Create(uint16_t group_id) { + return MakeUnique(EC_group_p521(), SSL_GROUP_SECP521R1); + case SSL_GROUP_X25519: return MakeUnique(); -+ case SSL_CURVE_X25519_KYBER512_DRAFT00: ++ case SSL_GROUP_X25519_KYBER512_DRAFT00: + return UniquePtr(New()); - case SSL_CURVE_X25519_KYBER768_DRAFT00: + case SSL_GROUP_X25519_KYBER768_DRAFT00: - return MakeUnique(); + return UniquePtr(New( + group_id)); -+ case SSL_CURVE_X25519_KYBER768_DRAFT00_OLD: ++ case SSL_GROUP_X25519_KYBER768_DRAFT00_OLD: + return UniquePtr(New( + group_id)); -+ case SSL_CURVE_P256_KYBER768_DRAFT00: ++ case SSL_GROUP_P256_KYBER768_DRAFT00: + return UniquePtr(New()); -+ case SSL_CURVE_X25519_MLKEM768: ++ case SSL_GROUP_X25519_MLKEM768: + return UniquePtr(New()); default: return nullptr; } -diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc -index 838761af5..9eb201d37 100644 ---- a/src/ssl/ssl_lib.cc -+++ b/src/ssl/ssl_lib.cc -@@ -3151,7 +3151,7 @@ namespace fips202205 { +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index 58b68e675..38c8e906c 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc +@@ -3260,7 +3260,7 @@ namespace fips202205 { // Section 3.3.1 // "The server shall be configured to only use cipher suites that are // composed entirely of NIST approved algorithms" --static const int kCurves[] = {NID_X9_62_prime256v1, NID_secp384r1}; -+static const int kCurves[] = {NID_P256Kyber768Draft00, NID_X9_62_prime256v1, NID_secp384r1}; +-static const uint16_t kGroups[] = {SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; ++static const uint16_t kGroups[] = {SSL_GROUP_P256_KYBER768_DRAFT00, SSL_GROUP_SECP256R1, SSL_GROUP_SECP384R1}; static const uint16_t kSigAlgs[] = { SSL_SIGN_RSA_PKCS1_SHA256, -diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc -index ef43a9e98..22178b5f6 100644 ---- a/src/ssl/ssl_test.cc -+++ b/src/ssl/ssl_test.cc -@@ -409,7 +409,34 @@ static const CurveTest kCurveTests[] = { +diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc +index a8f4f215b..e0ebb505e 100644 +--- a/ssl/ssl_test.cc ++++ b/ssl/ssl_test.cc +@@ -484,7 +484,34 @@ static const CurveTest kCurveTests[] = { "P-256:X25519Kyber768Draft00", - { SSL_CURVE_SECP256R1, SSL_CURVE_X25519_KYBER768_DRAFT00 }, + { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER768_DRAFT00 }, }, - + { + "Xyber512D00", -+ { SSL_CURVE_X25519_KYBER512_DRAFT00 }, ++ { SSL_GROUP_X25519_KYBER512_DRAFT00 }, + }, + { + "Xyber768D00", -+ { SSL_CURVE_X25519_KYBER768_DRAFT00 }, ++ { SSL_GROUP_X25519_KYBER768_DRAFT00 }, + }, + { + "Xyber768D00:Xyber768D00Old", -+ { SSL_CURVE_X25519_KYBER768_DRAFT00, SSL_CURVE_X25519_KYBER768_DRAFT00_OLD }, ++ { SSL_GROUP_X25519_KYBER768_DRAFT00, SSL_GROUP_X25519_KYBER768_DRAFT00_OLD }, + }, + { + "P-256:Xyber512D00", -+ { SSL_CURVE_SECP256R1, SSL_CURVE_X25519_KYBER512_DRAFT00 }, ++ { SSL_GROUP_SECP256R1, SSL_GROUP_X25519_KYBER512_DRAFT00 }, + }, + { + "P256Kyber768D00", -+ { SSL_CURVE_P256_KYBER768_DRAFT00 }, ++ { SSL_GROUP_P256_KYBER768_DRAFT00 }, + }, + { + "X25519MLKEM768", -+ { SSL_CURVE_X25519_MLKEM768 }, ++ { SSL_GROUP_X25519_MLKEM768 }, + }, + { + "P-256:P256Kyber768D00", -+ { SSL_CURVE_SECP256R1, SSL_CURVE_P256_KYBER768_DRAFT00 }, ++ { SSL_GROUP_SECP256R1, SSL_GROUP_P256_KYBER768_DRAFT00 }, + }, { "P-256:P-384:P-521:X25519", { -diff --git a/src/tool/speed.cc b/src/tool/speed.cc -index 5b0205953..6b3c67dab 100644 ---- a/src/tool/speed.cc -+++ b/src/tool/speed.cc -@@ -904,6 +904,116 @@ static bool SpeedScrypt(const std::string &selected) { +diff --git a/tool/speed.cc b/tool/speed.cc +index 942dcade1..f31e9e244 100644 +--- a/tool/speed.cc ++++ b/tool/speed.cc +@@ -1018,6 +1018,116 @@ static bool SpeedScrypt(const std::string &selected) { return true; } @@ -5067,7 +4722,7 @@ index 5b0205953..6b3c67dab 100644 static bool SpeedHRSS(const std::string &selected) { if (!selected.empty() && selected != "HRSS") { return true; -@@ -958,55 +1068,6 @@ static bool SpeedHRSS(const std::string &selected) { +@@ -1079,55 +1189,6 @@ static bool SpeedHRSS(const std::string &selected) { return true; } @@ -5078,39 +4733,39 @@ index 5b0205953..6b3c67dab 100644 - - TimeResults results; - -- KYBER_private_key priv; -- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; - uint8_t ciphertext[KYBER_CIPHERTEXT_BYTES]; - // This ciphertext is nonsense, but Kyber decap is constant-time so, for the - // purposes of timing, it's fine. - memset(ciphertext, 42, sizeof(ciphertext)); -- if (!TimeFunction(&results, -- [&priv, &encoded_public_key, &ciphertext]() -> bool { -- uint8_t shared_secret[32]; -- KYBER_generate_key(encoded_public_key, &priv); -- KYBER_decap(shared_secret, sizeof(shared_secret), -- ciphertext, &priv); -- return true; -- })) { +- if (!TimeFunctionParallel(&results, [&]() -> bool { +- KYBER_private_key priv; +- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; +- KYBER_generate_key(encoded_public_key, &priv); +- uint8_t shared_secret[32]; +- KYBER_decap(shared_secret, sizeof(shared_secret), ciphertext, &priv); +- return true; +- })) { - fprintf(stderr, "Failed to time KYBER_generate_key + KYBER_decap.\n"); - return false; - } - - results.Print("Kyber generate + decap"); - +- KYBER_private_key priv; +- uint8_t encoded_public_key[KYBER_PUBLIC_KEY_BYTES]; +- KYBER_generate_key(encoded_public_key, &priv); - KYBER_public_key pub; -- if (!TimeFunction( -- &results, [&pub, &ciphertext, &encoded_public_key]() -> bool { -- CBS encoded_public_key_cbs; -- CBS_init(&encoded_public_key_cbs, encoded_public_key, -- sizeof(encoded_public_key)); -- if (!KYBER_parse_public_key(&pub, &encoded_public_key_cbs)) { -- return false; -- } -- uint8_t shared_secret[32]; -- KYBER_encap(ciphertext, shared_secret, sizeof(shared_secret), &pub); -- return true; -- })) { +- if (!TimeFunctionParallel(&results, [&]() -> bool { +- CBS encoded_public_key_cbs; +- CBS_init(&encoded_public_key_cbs, encoded_public_key, +- sizeof(encoded_public_key)); +- if (!KYBER_parse_public_key(&pub, &encoded_public_key_cbs)) { +- return false; +- } +- uint8_t shared_secret[32]; +- KYBER_encap(ciphertext, shared_secret, sizeof(shared_secret), &pub); +- return true; +- })) { - fprintf(stderr, "Failed to time KYBER_encap.\n"); - return false; - } @@ -5120,19 +4775,16 @@ index 5b0205953..6b3c67dab 100644 - return true; -} - - static bool SpeedHashToCurve(const std::string &selected) { - if (!selected.empty() && selected.find("hashtocurve") == std::string::npos) { + static bool SpeedSpx(const std::string &selected) { + if (!selected.empty() && selected.find("spx") == std::string::npos) { return true; -@@ -1487,7 +1548,8 @@ bool Speed(const std::vector &args) { - !SpeedScrypt(selected) || - !SpeedRSAKeyGen(selected) || - !SpeedHRSS(selected) || -- !SpeedKyber(selected) || +@@ -1661,7 +1722,8 @@ bool Speed(const std::vector &args) { + !SpeedScrypt(selected) || // + !SpeedRSAKeyGen(selected) || // + !SpeedHRSS(selected) || // +- !SpeedKyber(selected) || // + !SpeedKyber512(selected) || + !SpeedKyber768(selected) || - !SpeedHashToCurve(selected) || + !SpeedSpx(selected) || // + !SpeedHashToCurve(selected) || // !SpeedTrustToken("TrustToken-Exp1-Batch1", TRUST_TOKEN_experiment_v1(), 1, - selected) || --- -2.46.0 - diff --git a/boring-sys/patches/rpk.patch b/boring-sys/patches/rpk.patch index bc2e3a8f..edf97708 100644 --- a/boring-sys/patches/rpk.patch +++ b/boring-sys/patches/rpk.patch @@ -1,7 +1,7 @@ -diff --git a/src/include/openssl/ssl.h b/src/include/openssl/ssl.h -index 53aa9b453..87309c3e1 100644 ---- a/src/include/openssl/ssl.h -+++ b/src/include/openssl/ssl.h +diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h +index 003e0a5f7..b8f8d49c8 100644 +--- a/include/openssl/ssl.h ++++ b/include/openssl/ssl.h @@ -138,6 +138,25 @@ * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR * OTHERWISE. @@ -28,7 +28,7 @@ index 53aa9b453..87309c3e1 100644 #ifndef OPENSSL_HEADER_SSL_H #define OPENSSL_HEADER_SSL_H -@@ -1136,6 +1155,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key( +@@ -1138,6 +1157,16 @@ OPENSSL_EXPORT int SSL_CTX_set_chain_and_key( SSL_CTX *ctx, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); @@ -45,7 +45,7 @@ index 53aa9b453..87309c3e1 100644 // SSL_set_chain_and_key sets the certificate chain and private key for a TLS // client or server. References to the given |CRYPTO_BUFFER| and |EVP_PKEY| // objects are added as needed. Exactly one of |privkey| or |privkey_method| -@@ -1144,6 +1173,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key( +@@ -1146,6 +1175,16 @@ OPENSSL_EXPORT int SSL_set_chain_and_key( SSL *ssl, CRYPTO_BUFFER *const *certs, size_t num_certs, EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method); @@ -62,8 +62,8 @@ index 53aa9b453..87309c3e1 100644 // SSL_CTX_get0_chain returns the list of |CRYPTO_BUFFER|s that were set by // |SSL_CTX_set_chain_and_key|. Reference counts are not incremented by this // call. The return value may be |NULL| if no chain has been set. -@@ -3023,6 +3062,21 @@ OPENSSL_EXPORT void SSL_get0_peer_application_settings(const SSL *ssl, - OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); +@@ -3041,6 +3080,21 @@ OPENSSL_EXPORT int SSL_has_application_settings(const SSL *ssl); + OPENSSL_EXPORT void SSL_set_alps_use_new_codepoint(SSL *ssl, int use_new); +// Server Certificate Type. @@ -84,10 +84,10 @@ index 53aa9b453..87309c3e1 100644 // Certificate compression. // // Certificates in TLS 1.3 can be compressed (RFC 8879). BoringSSL supports this -diff --git a/src/include/openssl/tls1.h b/src/include/openssl/tls1.h -index 772fb87a3..be605c1aa 100644 ---- a/src/include/openssl/tls1.h -+++ b/src/include/openssl/tls1.h +diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h +index c1207a3b7..ac6ed222a 100644 +--- a/include/openssl/tls1.h ++++ b/include/openssl/tls1.h @@ -146,6 +146,25 @@ * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR * OTHERWISE. @@ -124,10 +124,10 @@ index 772fb87a3..be605c1aa 100644 // ExtensionType value from RFC 7685 #define TLSEXT_TYPE_padding 21 -diff --git a/src/ssl/extensions.cc b/src/ssl/extensions.cc -index 5ee280221..2692e5478 100644 ---- a/src/ssl/extensions.cc -+++ b/src/ssl/extensions.cc +diff --git a/ssl/extensions.cc b/ssl/extensions.cc +index b13400097..8694712fd 100644 +--- a/ssl/extensions.cc ++++ b/ssl/extensions.cc @@ -105,6 +105,25 @@ * This product includes cryptographic software written by Eric Young * (eay@cryptsoft.com). This product includes software written by Tim @@ -154,7 +154,7 @@ index 5ee280221..2692e5478 100644 #include -@@ -3094,6 +3113,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, +@@ -3108,6 +3127,146 @@ bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert, return true; } @@ -301,9 +301,9 @@ index 5ee280221..2692e5478 100644 // kExtensions contains all the supported extensions. static const struct tls_extension kExtensions[] = { { -@@ -3267,6 +3426,13 @@ static const struct tls_extension kExtensions[] = { +@@ -3289,6 +3448,13 @@ static const struct tls_extension kExtensions[] = { ignore_parse_clienthello, - ext_alps_add_serverhello, + ext_alps_add_serverhello_old, }, + { + TLSEXT_TYPE_server_certificate_type, @@ -315,10 +315,10 @@ index 5ee280221..2692e5478 100644 }; #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension)) -diff --git a/src/ssl/handshake.cc b/src/ssl/handshake.cc -index 8d5a23872..b9ac70dfe 100644 ---- a/src/ssl/handshake.cc -+++ b/src/ssl/handshake.cc +diff --git a/ssl/handshake.cc b/ssl/handshake.cc +index 8d5a23872..c8ca629e8 100644 +--- a/ssl/handshake.cc ++++ b/ssl/handshake.cc @@ -109,6 +109,25 @@ * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. * ECC cipher suite support in OpenSSL originally developed by @@ -345,14 +345,14 @@ index 8d5a23872..b9ac70dfe 100644 #include -@@ -150,6 +169,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg) +@@ -148,6 +167,7 @@ SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg) + handback(false), + hints_requested(false), cert_compression_negotiated(false), + server_certificate_type_negotiated(false), apply_jdk11_workaround(false), can_release_private_key(false), channel_id_negotiated(false) { - assert(ssl); - @@ -365,7 +385,21 @@ enum ssl_verify_result_t ssl_verify_peer_cert(SSL_HANDSHAKE *hs) { uint8_t alert = SSL_AD_CERTIFICATE_UNKNOWN; @@ -376,10 +376,10 @@ index 8d5a23872..b9ac70dfe 100644 ret = hs->config->custom_verify_callback(ssl, &alert); switch (ret) { case ssl_verify_ok: -diff --git a/src/ssl/internal.h b/src/ssl/internal.h -index 1e6da2153..f04888384 100644 ---- a/src/ssl/internal.h -+++ b/src/ssl/internal.h +diff --git a/ssl/internal.h b/ssl/internal.h +index c9facb699..d7363e729 100644 +--- a/ssl/internal.h ++++ b/ssl/internal.h @@ -138,6 +138,25 @@ * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR * OTHERWISE. @@ -406,7 +406,7 @@ index 1e6da2153..f04888384 100644 #ifndef OPENSSL_HEADER_SSL_INTERNAL_H #define OPENSSL_HEADER_SSL_INTERNAL_H -@@ -1286,6 +1305,8 @@ int ssl_write_buffer_flush(SSL *ssl); +@@ -1311,6 +1330,8 @@ int ssl_write_buffer_flush(SSL *ssl); // configured. bool ssl_has_certificate(const SSL_HANDSHAKE *hs); @@ -415,7 +415,7 @@ index 1e6da2153..f04888384 100644 // ssl_parse_cert_chain parses a certificate list from |cbs| in the format used // by a TLS Certificate message. On success, it advances |cbs| and returns // true. Otherwise, it returns false and sets |*out_alert| to an alert to send -@@ -1887,6 +1908,8 @@ struct SSL_HANDSHAKE { +@@ -1912,6 +1933,8 @@ struct SSL_HANDSHAKE { // |cert_compression_negotiated| is true. uint16_t cert_compression_alg_id; @@ -424,7 +424,7 @@ index 1e6da2153..f04888384 100644 // ech_hpke_ctx is the HPKE context used in ECH. On the server, it is // initialized if |ech_status| is |ssl_ech_accepted|. On the client, it is // initialized if |selected_ech_config| is not nullptr. -@@ -2037,6 +2060,8 @@ struct SSL_HANDSHAKE { +@@ -2062,6 +2085,8 @@ struct SSL_HANDSHAKE { // cert_compression_negotiated is true iff |cert_compression_alg_id| is valid. bool cert_compression_negotiated : 1; @@ -433,7 +433,7 @@ index 1e6da2153..f04888384 100644 // apply_jdk11_workaround is true if the peer is probably a JDK 11 client // which implemented TLS 1.3 incorrectly. bool apply_jdk11_workaround : 1; -@@ -3049,6 +3074,9 @@ struct SSL_CONFIG { +@@ -3074,6 +3099,9 @@ struct SSL_CONFIG { // along with their corresponding ALPS values. GrowableArray alps_configs; @@ -443,7 +443,7 @@ index 1e6da2153..f04888384 100644 // Contains the QUIC transport params that this endpoint will send. Array quic_transport_params; -@@ -3648,6 +3676,9 @@ struct ssl_ctx_st { +@@ -3666,6 +3694,9 @@ struct ssl_ctx_st { // format. bssl::Array alpn_client_proto_list; @@ -453,10 +453,10 @@ index 1e6da2153..f04888384 100644 // SRTP profiles we are willing to do from RFC 5764 bssl::UniquePtr srtp_profiles; -diff --git a/src/ssl/ssl_cert.cc b/src/ssl/ssl_cert.cc +diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc index aa46a8bb6..d90840fce 100644 ---- a/src/ssl/ssl_cert.cc -+++ b/src/ssl/ssl_cert.cc +--- a/ssl/ssl_cert.cc ++++ b/ssl/ssl_cert.cc @@ -111,6 +111,25 @@ * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. * ECC cipher suite support in OpenSSL originally developed by @@ -573,10 +573,10 @@ index aa46a8bb6..d90840fce 100644 const STACK_OF(CRYPTO_BUFFER)* SSL_CTX_get0_chain(const SSL_CTX *ctx) { return ctx->cert->chain.get(); } -diff --git a/src/ssl/ssl_lib.cc b/src/ssl/ssl_lib.cc -index 838761af5..e4f1a12b7 100644 ---- a/src/ssl/ssl_lib.cc -+++ b/src/ssl/ssl_lib.cc +diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc +index 58b68e675..384debbd3 100644 +--- a/ssl/ssl_lib.cc ++++ b/ssl/ssl_lib.cc @@ -137,6 +137,25 @@ * SPECIFICALLY DISCLAIMS ANY LIABILITY FOR CLAIMS BROUGHT BY YOU OR ANY * OTHER ENTITY BASED ON INFRINGEMENT OF INTELLECTUAL PROPERTY RIGHTS OR @@ -615,8 +615,8 @@ index 838761af5..e4f1a12b7 100644 if (!ssl->method->ssl_new(ssl.get()) || !ssl->ctx->x509_method->ssl_new(ssl->s3->hs.get())) { return nullptr; -@@ -3140,6 +3164,53 @@ int SSL_CTX_set_tlsext_status_arg(SSL_CTX *ctx, void *arg) { - return 1; +@@ -3249,6 +3273,53 @@ int SSL_set1_curves_list(SSL *ssl, const char *curves) { + return SSL_set1_groups_list(ssl, curves); } +int SSL_CTX_set_server_raw_public_key_certificate(SSL_CTX *ctx, @@ -669,10 +669,10 @@ index 838761af5..e4f1a12b7 100644 namespace fips202205 { // (References are to SP 800-52r2): -diff --git a/src/ssl/tls13_both.cc b/src/ssl/tls13_both.cc +diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc index 5ab5a1c93..79135613e 100644 ---- a/src/ssl/tls13_both.cc -+++ b/src/ssl/tls13_both.cc +--- a/ssl/tls13_both.cc ++++ b/ssl/tls13_both.cc @@ -11,6 +11,25 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN @@ -750,10 +750,10 @@ index 5ab5a1c93..79135613e 100644 if (!ssl_has_certificate(hs)) { return ssl_add_message_cbb(ssl, cbb.get()); } -diff --git a/src/ssl/tls13_server.cc b/src/ssl/tls13_server.cc -index 9d26f4e00..a92689761 100644 ---- a/src/ssl/tls13_server.cc -+++ b/src/ssl/tls13_server.cc +diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc +index 707cf846b..6916606c2 100644 +--- a/ssl/tls13_server.cc ++++ b/ssl/tls13_server.cc @@ -11,6 +11,25 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN diff --git a/boring-sys/patches/underscore-wildcards.patch b/boring-sys/patches/underscore-wildcards.patch index f281b3a1..38e406a2 100644 --- a/boring-sys/patches/underscore-wildcards.patch +++ b/boring-sys/patches/underscore-wildcards.patch @@ -1,21 +1,10 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:underscore-wildcards ---- a/src/crypto/x509v3/v3_utl.c -+++ b/src/crypto/x509v3/v3_utl.c -@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, - // Check that the part matched by the wildcard contains only - // permitted characters and only matches a single label. - for (p = wildcard_start; p != wildcard_end; ++p) { -- if (!OPENSSL_isalnum(*p) && *p != '-') { -+ if (!OPENSSL_isalnum(*p) && *p != '-' && -+ !(*p == '_' && -+ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { - return 0; - } - } ---- a/src/crypto/x509/x509_test.cc -+++ b/src/crypto/x509/x509_test.cc -@@ -4500,6 +4500,31 @@ TEST(X509Test, Names) { +diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc +index 9699b5a75..b0e9b34a6 100644 +--- a/crypto/x509/x509_test.cc ++++ b/crypto/x509/x509_test.cc +@@ -4420,6 +4420,31 @@ TEST(X509Test, Names) { /*invalid_emails=*/{}, /*flags=*/0, }, @@ -47,9 +36,26 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders }; size_t i = 0; ---- a/src/include/openssl/x509c3.h -+++ b/src/include/openssl/x509v3.h -@@ -4497,6 +4497,8 @@ OPENSSL_EXPORT int X509_PURPOSE_get_id(const X509_PURPOSE *); +diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c +index bbc82e283..e61e1901d 100644 +--- a/crypto/x509v3/v3_utl.c ++++ b/crypto/x509v3/v3_utl.c +@@ -790,7 +790,9 @@ static int wildcard_match(const unsigned char *prefix, size_t prefix_len, + // Check that the part matched by the wildcard contains only + // permitted characters and only matches a single label. + for (p = wildcard_start; p != wildcard_end; ++p) { +- if (!OPENSSL_isalnum(*p) && *p != '-') { ++ if (!OPENSSL_isalnum(*p) && *p != '-' && ++ !(*p == '_' && ++ (flags & X509_CHECK_FLAG_UNDERSCORE_WILDCARDS))) { + return 0; + } + } +diff --git a/include/openssl/x509v3.h b/include/openssl/x509v3.h +index 2a2e02c2e..24e0604b0 100644 +--- a/include/openssl/x509v3.h ++++ b/include/openssl/x509v3.h +@@ -939,6 +939,8 @@ OPENSSL_EXPORT STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); #define X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS 0 // Skip the subject common name fallback if subjectAltNames is missing. #define X509_CHECK_FLAG_NEVER_CHECK_SUBJECT 0x20 @@ -58,4 +64,3 @@ https://github.com/google/boringssl/compare/master...cloudflare:boringssl:unders OPENSSL_EXPORT int X509_check_host(X509 *x, const char *chk, size_t chklen, unsigned int flags, char **peername); --- diff --git a/boring/Cargo.toml b/boring/Cargo.toml index f9a3527b..caac99f2 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -19,29 +19,11 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Controlling the build -# NOTE: This feature is deprecated. It is needed for the submoduled -# boringssl-fips, which is extremely old and requires modifications to the -# bindings, as some newer APIs don't exist and some function signatures have -# changed. It is highly recommended to use `fips-precompiled` instead. -# -# This feature sets `fips-compat` on behalf of the user to guarantee bindings -# compatibility with the submoduled boringssl-fips. -# # Use a FIPS-validated version of BoringSSL. -fips = ["fips-compat", "boring-sys/fips"] +fips = ["boring-sys/fips"] -# Build with compatibility for the submoduled boringssl-fips, without enabling -# the `fips` feature itself (useful e.g. if `fips-link-precompiled` is used -# with an older BoringSSL version). -fips-compat = [] - -# Use a precompiled FIPS-validated version of BoringSSL. Meant to be used with -# FIPS-20230428 or newer. Users must set `BORING_BSSL_FIPS_PATH` to use this -# feature, or else the build will fail. -fips-precompiled = ["boring-sys/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["boring-sys/fips-link-precompiled"] +# **DO NOT USE** This will be removed without warning in future releases. +legacy-compat-deprecated = [] # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) # This feature is necessary in order to compile the bindings for the diff --git a/boring/src/bio.rs b/boring/src/bio.rs index 71120606..2e6b2572 100644 --- a/boring/src/bio.rs +++ b/boring/src/bio.rs @@ -19,9 +19,9 @@ impl Drop for MemBioSlice<'_> { impl<'a> MemBioSlice<'a> { pub fn new(buf: &'a [u8]) -> Result, ErrorStack> { - #[cfg(not(feature = "fips-compat"))] + #[cfg(not(feature = "legacy-compat-deprecated"))] type BufLen = isize; - #[cfg(feature = "fips-compat")] + #[cfg(feature = "legacy-compat-deprecated")] type BufLen = libc::c_int; ffi::init(); diff --git a/boring/src/fips.rs b/boring/src/fips.rs index 8e451226..708b0903 100644 --- a/boring/src/fips.rs +++ b/boring/src/fips.rs @@ -15,16 +15,8 @@ pub fn enabled() -> bool { #[test] fn is_enabled() { - #[cfg(any( - feature = "fips", - feature = "fips-precompiled", - feature = "fips-link-precompiled" - ))] + #[cfg(feature = "fips")] assert!(enabled()); - #[cfg(not(any( - feature = "fips", - feature = "fips-precompiled", - feature = "fips-link-precompiled" - )))] + #[cfg(not(feature = "fips"))] assert!(!enabled()); } diff --git a/boring/src/lib.rs b/boring/src/lib.rs index 4b84b7c5..77f3e726 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -137,7 +137,6 @@ pub mod error; pub mod ex_data; pub mod fips; pub mod hash; -#[cfg(not(feature = "fips"))] pub mod hpke; pub mod memcmp; pub mod nid; diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 9cd40405..00fa5ba9 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -104,7 +104,6 @@ pub use self::async_callbacks::{ pub use self::connector::{ ConnectConfiguration, SslAcceptor, SslAcceptorBuilder, SslConnector, SslConnectorBuilder, }; -#[cfg(not(feature = "fips"))] pub use self::ech::{SslEchKeys, SslEchKeysRef}; pub use self::error::{Error, ErrorCode, HandshakeError}; @@ -112,7 +111,6 @@ mod async_callbacks; mod bio; mod callbacks; mod connector; -#[cfg(not(feature = "fips"))] mod ech; mod error; mod mut_only; @@ -708,45 +706,32 @@ pub struct SslCurveNid(c_int); pub struct SslCurve(c_int); impl SslCurve { - pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP224R1 as _); + pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP224R1 as _); - pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP256R1 as _); + pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP256R1 as _); - pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP384R1 as _); + pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP384R1 as _); - pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_CURVE_SECP521R1 as _); + pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP521R1 as _); - pub const X25519: SslCurve = SslCurve(ffi::SSL_CURVE_X25519 as _); + pub const X25519: SslCurve = SslCurve(ffi::SSL_GROUP_X25519 as _); - #[cfg(not(any(feature = "fips", feature = "fips-precompiled")))] pub const X25519_KYBER768_DRAFT00: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 as _); + SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] + #[cfg(feature = "pq-experimental")] pub const X25519_KYBER768_DRAFT00_OLD: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD as _); + SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] + #[cfg(feature = "pq-experimental")] pub const X25519_KYBER512_DRAFT00: SslCurve = - SslCurve(ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 as _); + SslCurve(ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_CURVE_P256_KYBER768_DRAFT00 as _); + #[cfg(feature = "pq-experimental")] + pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_GROUP_P256_KYBER768_DRAFT00 as _); - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_CURVE_X25519_MLKEM768 as _); + #[cfg(feature = "pq-experimental")] + pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_GROUP_X25519_MLKEM768 as _); /// Returns the curve name #[corresponds(SSL_get_curve_name)] @@ -766,7 +751,7 @@ impl SslCurve { // against the absence of the `kx-safe-default` feature and thus this function is never used. // // **NOTE**: This function only exists because the version of boringssl we currently use does - // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_CURVE id + // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_GROUP id // as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal // NID, we need this mapping in place to avoid breaking changes to the public API. Once the // underlying boringssl version is upgraded, this should be removed in favor of the new @@ -774,33 +759,20 @@ impl SslCurve { #[allow(dead_code)] pub fn nid(&self) -> Option { match self.0 { - ffi::SSL_CURVE_SECP224R1 => Some(ffi::NID_secp224r1), - ffi::SSL_CURVE_SECP256R1 => Some(ffi::NID_X9_62_prime256v1), - ffi::SSL_CURVE_SECP384R1 => Some(ffi::NID_secp384r1), - ffi::SSL_CURVE_SECP521R1 => Some(ffi::NID_secp521r1), - ffi::SSL_CURVE_X25519 => Some(ffi::NID_X25519), - #[cfg(not(any(feature = "fips", feature = "fips-precompiled")))] - ffi::SSL_CURVE_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00), - #[cfg(all( - not(any(feature = "fips", feature = "fips-precompiled")), - feature = "pq-experimental" - ))] - ffi::SSL_CURVE_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), + ffi::SSL_GROUP_SECP224R1 => Some(ffi::NID_secp224r1), + ffi::SSL_GROUP_SECP256R1 => Some(ffi::NID_X9_62_prime256v1), + ffi::SSL_GROUP_SECP384R1 => Some(ffi::NID_secp384r1), + ffi::SSL_GROUP_SECP521R1 => Some(ffi::NID_secp521r1), + ffi::SSL_GROUP_X25519 => Some(ffi::NID_X25519), + ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00), + #[cfg(feature = "pq-experimental")] + ffi::SSL_GROUP_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), _ => None, } .map(SslCurveNid) @@ -809,12 +781,11 @@ impl SslCurve { /// A compliance policy. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg(not(feature = "fips-compat"))] pub struct CompliancePolicy(ffi::ssl_compliance_policy_t); -#[cfg(not(feature = "fips-compat"))] impl CompliancePolicy { /// Does nothing, however setting this does not undo other policies, so trying to set this is an error. + #[cfg(not(feature = "legacy-compat-deprecated"))] 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. @@ -824,6 +795,7 @@ impl CompliancePolicy { /// 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. + #[cfg(not(feature = "legacy-compat-deprecated"))] pub const WPA3_192_202304: Self = Self(ffi::ssl_compliance_policy_t::ssl_compliance_policy_wpa3_192_202304); } @@ -1609,7 +1581,10 @@ impl SslContextBuilder { #[corresponds(SSL_CTX_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))] + #[cfg_attr( + not(feature = "legacy-compat-deprecated"), + allow(clippy::unnecessary_cast) + )] { assert!(protocols.len() <= ProtosLen::MAX as usize); } @@ -2009,7 +1984,6 @@ impl SslContextBuilder { /// version of BoringSSL which doesn't yet include these APIs. /// Once the submoduled fips commit is upgraded, these gates can be removed. #[corresponds(SSL_CTX_set_permute_extensions)] - #[cfg(not(feature = "fips-compat"))] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_CTX_set_permute_extensions(self.as_ptr(), enabled as _) } } @@ -2087,7 +2061,6 @@ impl SslContextBuilder { /// /// This feature isn't available in the certified version of BoringSSL. #[corresponds(SSL_CTX_set_compliance_policy)] - #[cfg(not(feature = "fips-compat"))] 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(|_| ()) } } @@ -2108,7 +2081,6 @@ impl SslContextBuilder { /// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function /// is safe to call even after the `SSL_CTX` has been associated with connections on various /// threads. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } @@ -2376,7 +2348,6 @@ impl SslContextRef { /// ECHConfigs to allow stale DNS caches to update. Unlike most `SSL_CTX` APIs, this function /// is safe to call even after the `SSL_CTX` has been associated with connections on various /// threads. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_CTX_set1_ech_keys)] pub fn set_ech_keys(&self, keys: &SslEchKeys) -> Result<(), ErrorStack> { unsafe { cvt(ffi::SSL_CTX_set1_ech_keys(self.as_ptr(), keys.as_ptr())).map(|_| ()) } @@ -2390,9 +2361,9 @@ impl SslContextRef { #[derive(Debug)] pub struct GetSessionPendingError; -#[cfg(not(feature = "fips-compat"))] +#[cfg(not(feature = "legacy-compat-deprecated"))] type ProtosLen = usize; -#[cfg(feature = "fips-compat")] +#[cfg(feature = "legacy-compat-deprecated")] type ProtosLen = libc::c_uint; /// Information about the state of a cipher. @@ -3161,7 +3132,6 @@ impl SslRef { /// Note: This is gated to non-fips because the fips feature builds with a separate /// version of BoringSSL which doesn't yet include these APIs. /// Once the submoduled fips commit is upgraded, these gates can be removed. - #[cfg(not(feature = "fips-compat"))] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_set_permute_extensions(self.as_ptr(), enabled as _) } } @@ -3172,7 +3142,10 @@ impl SslRef { #[corresponds(SSL_set_alpn_protos)] pub fn set_alpn_protos(&mut self, protocols: &[u8]) -> Result<(), ErrorStack> { unsafe { - #[cfg_attr(not(feature = "fips-compat"), allow(clippy::unnecessary_cast))] + #[cfg_attr( + not(feature = "legacy-compat-deprecated"), + allow(clippy::unnecessary_cast) + )] { assert!(protocols.len() <= ProtosLen::MAX as usize); } @@ -3886,7 +3859,6 @@ impl SslRef { /// Clients should use `get_ech_name_override` to verify the server certificate in case of ECH /// rejection, and follow up with `get_ech_retry_configs` to retry the connection with a fresh /// set of ECHConfigs. If the retry also fails, clients should report a connection failure. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_set1_ech_config_list)] pub fn set_ech_config_list(&mut self, ech_config_list: &[u8]) -> Result<(), ErrorStack> { unsafe { @@ -3905,7 +3877,6 @@ impl SslRef { /// Clients should call this function when handling an `SSL_R_ECH_REJECTED` error code to /// recover from potential key mismatches. If the result is `Some`, the client should retry the /// connection using the returned `ECHConfigList`. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_get0_ech_retry_configs)] #[must_use] pub fn get_ech_retry_configs(&self) -> Option<&[u8]> { @@ -3928,7 +3899,6 @@ impl SslRef { /// Clients should call this function during the certificate verification callback to /// ensure the server's certificate is valid for the public name, which is required to /// authenticate retry configs. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_get0_ech_name_override)] #[must_use] pub fn get_ech_name_override(&self) -> Option<&[u8]> { @@ -3946,7 +3916,6 @@ impl SslRef { } // Whether or not `SSL` negotiated ECH. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_ech_accepted)] #[must_use] pub fn ech_accepted(&self) -> bool { @@ -3954,7 +3923,6 @@ impl SslRef { } // Whether or not to enable ECH grease on `SSL`. - #[cfg(not(feature = "fips"))] #[corresponds(SSL_set_enable_ech_grease)] pub fn set_enable_ech_grease(&self, enable: bool) { let enable = if enable { 1 } else { 0 }; @@ -3965,7 +3933,6 @@ impl SslRef { } /// Sets the compliance policy on `SSL`. - #[cfg(not(feature = "fips-compat"))] #[corresponds(SSL_set_compliance_policy)] pub fn set_compliance_policy(&mut self, policy: CompliancePolicy) -> Result<(), ErrorStack> { unsafe { cvt_0i(ffi::SSL_set_compliance_policy(self.as_ptr(), policy.0)).map(|_| ()) } diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 9b7b024c..6ac6ca75 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -22,13 +22,11 @@ use crate::x509::store::X509StoreBuilder; use crate::x509::verify::X509CheckFlags; use crate::x509::{X509Name, X509}; -#[cfg(not(feature = "fips"))] use super::CompliancePolicy; mod cert_compressor; mod cert_verify; mod custom_verify; -#[cfg(not(feature = "fips"))] mod ech; mod private_key_method; mod server; @@ -1037,7 +1035,6 @@ fn test_get_ciphers() { } #[test] -#[cfg(not(feature = "fips"))] fn test_set_compliance() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); ctx.set_compliance_policy(CompliancePolicy::FIPS_202205) @@ -1118,7 +1115,6 @@ fn test_info_callback() { assert!(CALLED_BACK.load(Ordering::Relaxed)); } -#[cfg(not(feature = "fips-compat"))] #[test] fn test_ssl_set_compliance() { let ctx = SslContext::builder(SslMethod::tls()).unwrap().build(); diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 8bf96d33..17115d68 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -1120,9 +1120,9 @@ impl X509NameBuilder { } } -#[cfg(not(feature = "fips-compat"))] +#[cfg(not(feature = "legacy-compat-deprecated"))] type ValueLen = isize; -#[cfg(feature = "fips-compat")] +#[cfg(feature = "legacy-compat-deprecated")] type ValueLen = i32; foreign_type_and_impl_send_sync! { diff --git a/boring/src/x509/tests/trusted_first.rs b/boring/src/x509/tests/trusted_first.rs index b5e714b5..9f49ffe3 100644 --- a/boring/src/x509/tests/trusted_first.rs +++ b/boring/src/x509/tests/trusted_first.rs @@ -15,7 +15,7 @@ fn test_verify_cert() { assert_eq!(Ok(()), verify(&leaf, &[&root1], &[&intermediate], |_| {})); - #[cfg(not(feature = "fips-compat"))] + #[cfg(not(feature = "legacy-compat-deprecated"))] assert_eq!( Ok(()), verify( @@ -26,7 +26,7 @@ fn test_verify_cert() { ) ); - #[cfg(feature = "fips-compat")] + #[cfg(feature = "legacy-compat-deprecated")] assert_eq!( Err(X509VerifyError::CERT_HAS_EXPIRED), verify( diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 77a8a788..25f360fd 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -17,20 +17,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] # Use a FIPS-validated version of boringssl. -fips = ["tokio-boring/fips"] - -# Use a FIPS build of BoringSSL, but don't set "fips-compat". -# -# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer -# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen, -# which means the "fips-compat" feature is no longer needed. -# -# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply -# "fips-compat". -fips-precompiled = ["tokio-boring/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["tokio-boring/fips-link-precompiled"] +fips = ["boring/fips", "tokio-boring/fips"] # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["tokio-boring/pq-experimental"] diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index 75c64129..c5735341 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -19,19 +19,6 @@ rustdoc-args = ["--cfg", "docsrs"] # Use a FIPS-validated version of boringssl. fips = ["boring/fips", "boring-sys/fips"] -# Use a FIPS build of BoringSSL, but don't set "fips-compat". -# -# As of boringSSL commit a430310d6563c0734ddafca7731570dfb683dc19, we no longer -# need to make exceptions for the types of BufLen, ProtosLen, and ValueLen, -# which means the "fips-compat" feature is no longer needed. -# -# TODO(cjpatton) Delete this feature and modify "fips" so that it doesn't imply -# "fips-compat". -fips-precompiled = ["boring/fips-precompiled"] - -# Link with precompiled FIPS-validated `bcm.o` module. -fips-link-precompiled = ["boring/fips-link-precompiled", "boring-sys/fips-link-precompiled"] - # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["boring/pq-experimental"] From 0fc992bd76632c51fcb8028d2abd0fca5c4277ad Mon Sep 17 00:00:00 2001 From: Rushil Mehra Date: Wed, 19 Feb 2025 00:47:00 -0800 Subject: [PATCH 13/39] Align SslStream APIs with upstream SslStream::new() is fallible, but `SslStream::from_raw_parts()` and `SslStreamBuilder::new()` now unwrap. Upstream has also deprecated the `SslStreamBuilder`, maybe we should do the same. --- boring/src/ssl/mod.rs | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 00fa5ba9..baefe9bf 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -4040,26 +4040,23 @@ where } impl SslStream { - fn new_base(ssl: Ssl, stream: S) -> Self { - unsafe { - let (bio, method) = bio::new(stream).unwrap(); - ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); - - SslStream { - ssl: ManuallyDrop::new(ssl), - method: ManuallyDrop::new(method), - _p: PhantomData, - } - } - } - /// Creates a new `SslStream`. /// /// This function performs no IO; the stream will not have performed any part of the handshake /// with the peer. The `connect` and `accept` methods can be used to /// explicitly perform the handshake. pub fn new(ssl: Ssl, stream: S) -> Result { - Ok(Self::new_base(ssl, stream)) + let (bio, method) = bio::new(stream)?; + + unsafe { + ffi::SSL_set_bio(ssl.as_ptr(), bio, bio); + } + + Ok(SslStream { + ssl: ManuallyDrop::new(ssl), + method: ManuallyDrop::new(method), + _p: PhantomData, + }) } /// Constructs an `SslStream` from a pointer to the underlying OpenSSL `SSL` struct. @@ -4071,7 +4068,7 @@ impl SslStream { /// The caller must ensure the pointer is valid. pub unsafe fn from_raw_parts(ssl: *mut ffi::SSL, stream: S) -> Self { let ssl = Ssl::from_ptr(ssl); - Self::new_base(ssl, stream) + Self::new(ssl, stream).unwrap() } /// Like `read`, but takes a possibly-uninitialized slice. @@ -4338,7 +4335,7 @@ where /// Begin creating an `SslStream` atop `stream` pub fn new(ssl: Ssl, stream: S) -> Self { Self { - inner: SslStream::new_base(ssl, stream), + inner: SslStream::new(ssl, stream).unwrap(), } } From 8abba360d35c3640f433289c8e661bec84bbe685 Mon Sep 17 00:00:00 2001 From: Rushil Mehra Date: Wed, 19 Feb 2025 00:54:03 -0800 Subject: [PATCH 14/39] `Ssl::new_from_ref` -> `Ssl::new()` --- boring/src/ssl/mod.rs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index baefe9bf..49788c3e 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2749,32 +2749,13 @@ impl Ssl { } } - /// Creates a new `Ssl`. - /// - // FIXME should take &SslContextRef - #[corresponds(SSL_new)] - pub fn new(ctx: &SslContext) -> Result { - unsafe { - let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?; - let mut ssl = Ssl::from_ptr(ptr); - ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.clone()); - - Ok(ssl) - } - } - /// Creates a new [`Ssl`]. - /// - /// This function does the same as [`Self:new`] except that it takes &[SslContextRef]. - // Both functions exist for backward compatibility (no breaking API). #[corresponds(SSL_new)] - pub fn new_from_ref(ctx: &SslContextRef) -> Result { + pub fn new(ctx: &SslContextRef) -> Result { unsafe { let ptr = cvt_p(ffi::SSL_new(ctx.as_ptr()))?; let mut ssl = Ssl::from_ptr(ptr); - SSL_CTX_up_ref(ctx.as_ptr()); - let ctx_owned = SslContext::from_ptr(ctx.as_ptr()); - ssl.set_ex_data(*SESSION_CTX_INDEX, ctx_owned); + ssl.set_ex_data(*SESSION_CTX_INDEX, ctx.to_owned()); Ok(ssl) } From 646ae33c613dfeca4cffd95540e9c9a7d7489a37 Mon Sep 17 00:00:00 2001 From: Rushil Mehra Date: Wed, 19 Feb 2025 01:25:20 -0800 Subject: [PATCH 15/39] X509Builder::append_extension2 -> X509Builder::append_extension --- boring/examples/mk_certs.rs | 18 ++++++++++-------- boring/src/pkcs12.rs | 2 +- boring/src/x509/mod.rs | 9 +-------- boring/src/x509/tests/mod.rs | 14 ++++++++------ 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/boring/examples/mk_certs.rs b/boring/examples/mk_certs.rs index 1fc4993d..6b4c6e01 100644 --- a/boring/examples/mk_certs.rs +++ b/boring/examples/mk_certs.rs @@ -43,18 +43,19 @@ fn mk_ca_cert() -> Result<(X509, PKey), ErrorStack> { let not_after = Asn1Time::days_from_now(365)?; cert_builder.set_not_after(¬_after)?; - cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?; + cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?.as_ref())?; cert_builder.append_extension( KeyUsage::new() .critical() .key_cert_sign() .crl_sign() - .build()?, + .build()? + .as_ref(), )?; let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?; - cert_builder.append_extension(subject_key_identifier)?; + cert_builder.append_extension(&subject_key_identifier)?; cert_builder.sign(&privkey, MessageDigest::sha256())?; let cert = cert_builder.build(); @@ -106,7 +107,7 @@ fn mk_ca_signed_cert( let not_after = Asn1Time::days_from_now(365)?; cert_builder.set_not_after(¬_after)?; - cert_builder.append_extension(BasicConstraints::new().build()?)?; + cert_builder.append_extension(BasicConstraints::new().build()?.as_ref())?; cert_builder.append_extension( KeyUsage::new() @@ -114,24 +115,25 @@ fn mk_ca_signed_cert( .non_repudiation() .digital_signature() .key_encipherment() - .build()?, + .build()? + .as_ref(), )?; let subject_key_identifier = SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(subject_key_identifier)?; + cert_builder.append_extension(&subject_key_identifier)?; let auth_key_identifier = AuthorityKeyIdentifier::new() .keyid(false) .issuer(false) .build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(auth_key_identifier)?; + cert_builder.append_extension(&auth_key_identifier)?; let subject_alt_name = SubjectAlternativeName::new() .dns("*.example.com") .dns("hello.com") .build(&cert_builder.x509v3_context(Some(ca_cert), None))?; - cert_builder.append_extension(subject_alt_name)?; + cert_builder.append_extension(&subject_alt_name)?; cert_builder.sign(ca_privkey, MessageDigest::sha256())?; let cert = cert_builder.build(); diff --git a/boring/src/pkcs12.rs b/boring/src/pkcs12.rs index dd255e3a..e8fb7c12 100644 --- a/boring/src/pkcs12.rs +++ b/boring/src/pkcs12.rs @@ -260,7 +260,7 @@ mod test { .unwrap(); builder.set_subject_name(&name).unwrap(); builder.set_issuer_name(&name).unwrap(); - builder.append_extension(key_usage).unwrap(); + builder.append_extension(&key_usage).unwrap(); builder.set_pubkey(&pkey).unwrap(); builder.sign(&pkey, MessageDigest::sha256()).unwrap(); let cert = builder.build(); diff --git a/boring/src/x509/mod.rs b/boring/src/x509/mod.rs index 17115d68..cd5d428e 100644 --- a/boring/src/x509/mod.rs +++ b/boring/src/x509/mod.rs @@ -484,16 +484,9 @@ impl X509Builder { } } - /// Adds an X509 extension value to the certificate. - /// - /// This works just as `append_extension` except it takes ownership of the `X509Extension`. - pub fn append_extension(&mut self, extension: X509Extension) -> Result<(), ErrorStack> { - self.append_extension2(&extension) - } - /// Adds an X509 extension value to the certificate. #[corresponds(X509_add_ext)] - pub fn append_extension2(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { + pub fn append_extension(&mut self, extension: &X509ExtensionRef) -> Result<(), ErrorStack> { unsafe { cvt(ffi::X509_add_ext(self.0.as_ptr(), extension.as_ptr(), -1))?; Ok(()) diff --git a/boring/src/x509/tests/mod.rs b/boring/src/x509/tests/mod.rs index 0ab054ab..371cd9b6 100644 --- a/boring/src/x509/tests/mod.rs +++ b/boring/src/x509/tests/mod.rs @@ -250,34 +250,36 @@ fn x509_builder() { .unwrap(); let basic_constraints = BasicConstraints::new().critical().ca().build().unwrap(); - builder.append_extension(basic_constraints).unwrap(); + builder + .append_extension(basic_constraints.as_ref()) + .unwrap(); let key_usage = KeyUsage::new() .digital_signature() .key_encipherment() .build() .unwrap(); - builder.append_extension(key_usage).unwrap(); + builder.append_extension(&key_usage).unwrap(); let ext_key_usage = ExtendedKeyUsage::new() .client_auth() .server_auth() .other("2.999.1") .build() .unwrap(); - builder.append_extension(ext_key_usage).unwrap(); + builder.append_extension(&ext_key_usage).unwrap(); let subject_key_identifier = SubjectKeyIdentifier::new() .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(subject_key_identifier).unwrap(); + builder.append_extension(&subject_key_identifier).unwrap(); let authority_key_identifier = AuthorityKeyIdentifier::new() .keyid(true) .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(authority_key_identifier).unwrap(); + builder.append_extension(&authority_key_identifier).unwrap(); let subject_alternative_name = SubjectAlternativeName::new() .dns("example.com") .build(&builder.x509v3_context(None, None)) .unwrap(); - builder.append_extension(subject_alternative_name).unwrap(); + builder.append_extension(&subject_alternative_name).unwrap(); builder.sign(&pkey, MessageDigest::sha256()).unwrap(); From 72dabe1d8577b090a6cd7b0560ce8f4bae6c396c Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Mon, 29 Sep 2025 17:16:57 -0700 Subject: [PATCH 16/39] Remove the "kx-*" features The "kx-*" features control default key exchange preferences. Its implementation requires disabling APIs for manually setting curve preferences via `set_curves()` or `set_curves_list()`. In practice, most teams need to be able to override default preferences at runtime anyway, which means these features were never really used. This commit gets rid of them, thereby reducing some complexity in the API. --- .github/workflows/ci.yml | 2 -- boring/Cargo.toml | 22 ---------------- boring/src/ssl/mod.rs | 54 -------------------------------------- boring/src/ssl/test/mod.rs | 25 ------------------ 4 files changed, 103 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ee8b46c..46e621e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -363,8 +363,6 @@ jobs: name: Run `underscore-wildcards` tests - run: cargo test --features pq-experimental,rpk name: Run `pq-experimental,rpk` tests - - run: cargo test --features kx-safe-default,pq-experimental - name: Run `kx-safe-default` tests - run: cargo test --features pq-experimental,underscore-wildcards name: Run `pq-experimental,underscore-wildcards` tests - run: cargo test --features rpk,underscore-wildcards diff --git a/boring/Cargo.toml b/boring/Cargo.toml index caac99f2..bc9dba22 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -44,28 +44,6 @@ pq-experimental = ["boring-sys/pq-experimental"] # those for `pq-experimental` feature apply. underscore-wildcards = ["boring-sys/underscore-wildcards"] -# Controlling key exchange preferences at compile time - -# Choose key exchange preferences at compile time. This prevents the user from -# choosing their own preferences. -kx-safe-default = [] - -# Support PQ key exchange. The client will prefer classical key exchange, but -# will upgrade to PQ key exchange if requested by the server. This is the -# safest option if you don't know if the peer supports PQ key exchange. This -# feature implies "kx-safe-default". -kx-client-pq-supported = ["kx-safe-default"] - -# Prefer PQ key exchange. The client will prefer PQ exchange, but fallback to -# classical key exchange if requested by the server. This is the best option if -# you know the peer supports PQ key exchange. This feature implies -# "kx-safe-default" and "kx-client-pq-supported". -kx-client-pq-preferred = ["kx-safe-default", "kx-client-pq-supported"] - -# Disable key exchange involving non-NIST key exchange on the client side. -# Implies "kx-safe-default". -kx-client-nist-required = ["kx-safe-default"] - [dependencies] bitflags = { workspace = true } foreign-types = { workspace = true } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 49788c3e..302f99ba 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -747,16 +747,12 @@ impl SslCurve { } } - // We need to allow dead_code here because `SslRef::set_curves` is conditionally compiled - // against the absence of the `kx-safe-default` feature and thus this function is never used. - // // **NOTE**: This function only exists because the version of boringssl we currently use does // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_GROUP id // as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal // NID, we need this mapping in place to avoid breaking changes to the public API. Once the // underlying boringssl version is upgraded, this should be removed in favor of the new // SSL_CTX_set1_group_ids API. - #[allow(dead_code)] pub fn nid(&self) -> Option { match self.0 { ffi::SSL_GROUP_SECP224R1 => Some(ffi::NID_secp224r1), @@ -2017,11 +2013,6 @@ impl SslContextBuilder { } /// Sets the context's supported curves. - // - // If the "kx-*" flags are used to set key exchange preference, then don't allow the user to - // set them here. This ensures we don't override the user's preference without telling them: - // when the flags are used, the preferences are set just before connecting or accepting. - #[cfg(not(feature = "kx-safe-default"))] #[corresponds(SSL_CTX_set1_curves_list)] pub fn set_curves_list(&mut self, curves: &str) -> Result<(), ErrorStack> { let curves = CString::new(curves).map_err(ErrorStack::internal_error)?; @@ -2035,12 +2026,7 @@ impl SslContextBuilder { } /// Sets the context's supported curves. - // - // If the "kx-*" flags are used to set key exchange preference, then don't allow the user to - // set them here. This ensures we don't override the user's preference without telling them: - // when the flags are used, the preferences are set just before connecting or accepting. #[corresponds(SSL_CTX_set1_curves)] - #[cfg(not(feature = "kx-safe-default"))] pub fn set_curves(&mut self, curves: &[SslCurve]) -> Result<(), ErrorStack> { let curves: Vec = curves .iter() @@ -2915,40 +2901,6 @@ impl SslRef { } } - #[cfg(feature = "kx-safe-default")] - fn client_set_default_curves_list(&mut self) { - let curves = if cfg!(feature = "kx-client-pq-preferred") { - if cfg!(feature = "kx-client-nist-required") { - "P256Kyber768Draft00:P-256:P-384:P-521" - } else { - "X25519MLKEM768:X25519Kyber768Draft00:X25519:P256Kyber768Draft00:P-256:P-384:P-521" - } - } else if cfg!(feature = "kx-client-pq-supported") { - if cfg!(feature = "kx-client-nist-required") { - "P-256:P-384:P-521:P256Kyber768Draft00" - } else { - "X25519:P-256:P-384:P-521:X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00" - } - } else { - if cfg!(feature = "kx-client-nist-required") { - "P-256:P-384:P-521" - } else { - "X25519:P-256:P-384:P-521" - } - }; - - self.set_curves_list(curves) - .expect("invalid default client curves list"); - } - - #[cfg(feature = "kx-safe-default")] - fn server_set_default_curves_list(&mut self) { - self.set_curves_list( - "X25519MLKEM768:X25519Kyber768Draft00:P256Kyber768Draft00:X25519:P-256:P-384", - ) - .expect("invalid default server curves list"); - } - /// Returns the [`SslCurve`] used for this `SslRef`. #[corresponds(SSL_get_curve_id)] #[must_use] @@ -4341,9 +4293,6 @@ where pub fn setup_connect(mut self) -> MidHandshakeSslStream { self.set_connect_state(); - #[cfg(feature = "kx-safe-default")] - self.inner.ssl.client_set_default_curves_list(); - MidHandshakeSslStream { stream: self.inner, error: Error { @@ -4373,9 +4322,6 @@ where pub fn setup_accept(mut self) -> MidHandshakeSslStream { self.set_accept_state(); - #[cfg(feature = "kx-safe-default")] - self.inner.ssl.server_set_default_curves_list(); - MidHandshakeSslStream { stream: self.inner, error: Error { diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 6ac6ca75..0a4f6243 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -952,30 +952,6 @@ fn sni_callback_swapped_ctx() { assert!(CALLED_BACK.load(Ordering::SeqCst)); } -#[cfg(feature = "kx-safe-default")] -#[test] -fn client_set_default_curves_list() { - let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls()) - .unwrap() - .build(); - let mut ssl = Ssl::new(&ssl_ctx).unwrap(); - - // Panics if Kyber768 missing in boringSSL. - ssl.client_set_default_curves_list(); -} - -#[cfg(feature = "kx-safe-default")] -#[test] -fn server_set_default_curves_list() { - let ssl_ctx = crate::ssl::SslContextBuilder::new(SslMethod::tls()) - .unwrap() - .build(); - let mut ssl = Ssl::new(&ssl_ctx).unwrap(); - - // Panics if Kyber768 missing in boringSSL. - ssl.server_set_default_curves_list(); -} - #[test] fn get_curve() { let server = Server::builder().build(); @@ -994,7 +970,6 @@ fn get_curve_name() { assert_eq!(SslCurve::X25519.name(), Some("X25519")); } -#[cfg(not(feature = "kx-safe-default"))] #[test] fn set_curves() { let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); From 21735accf89606c54d9150eacb2852a9f42fd9e2 Mon Sep 17 00:00:00 2001 From: Bas Westerbaan Date: Tue, 30 Sep 2025 11:56:56 +0200 Subject: [PATCH 17/39] pq: fix MSVC C4146 warning --- boring-sys/patches/boring-pq.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index fb5df8b9..1f13962a 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -503,7 +503,7 @@ index d3ea02090..ccb5b3d9b 100644 + for(i=0;i> 63; ++ return (0-(uint64_t)r) >> 63; +} + +/************************************************* From b46d77087e38cbeafb7767ead0b8db84ca9bb887 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Mon, 29 Sep 2025 16:10:50 -0700 Subject: [PATCH 18/39] Remove `SslCurve` API This is incompatible with the latest internal FIPS build. Namely, the various group identifiers have been renamed since the previous version. --- boring/src/ssl/mod.rs | 123 ------------------------------------- boring/src/ssl/test/mod.rs | 33 +--------- 2 files changed, 1 insertion(+), 155 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 302f99ba..574f001d 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -695,86 +695,6 @@ impl From for SslSignatureAlgorithm { } } -/// Numeric identifier of a TLS curve. -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct SslCurveNid(c_int); - -/// A TLS Curve. -#[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct SslCurve(c_int); - -impl SslCurve { - pub const SECP224R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP224R1 as _); - - pub const SECP256R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP256R1 as _); - - pub const SECP384R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP384R1 as _); - - pub const SECP521R1: SslCurve = SslCurve(ffi::SSL_GROUP_SECP521R1 as _); - - pub const X25519: SslCurve = SslCurve(ffi::SSL_GROUP_X25519 as _); - - pub const X25519_KYBER768_DRAFT00: SslCurve = - SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 as _); - - #[cfg(feature = "pq-experimental")] - pub const X25519_KYBER768_DRAFT00_OLD: SslCurve = - SslCurve(ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD as _); - - #[cfg(feature = "pq-experimental")] - pub const X25519_KYBER512_DRAFT00: SslCurve = - SslCurve(ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 as _); - - #[cfg(feature = "pq-experimental")] - pub const P256_KYBER768_DRAFT00: SslCurve = SslCurve(ffi::SSL_GROUP_P256_KYBER768_DRAFT00 as _); - - #[cfg(feature = "pq-experimental")] - pub const X25519_MLKEM768: SslCurve = SslCurve(ffi::SSL_GROUP_X25519_MLKEM768 as _); - - /// Returns the curve name - #[corresponds(SSL_get_curve_name)] - #[must_use] - pub fn name(&self) -> Option<&'static str> { - unsafe { - let ptr = ffi::SSL_get_curve_name(self.0 as u16); - if ptr.is_null() { - return None; - } - - CStr::from_ptr(ptr).to_str().ok() - } - } - - // **NOTE**: This function only exists because the version of boringssl we currently use does - // not expose SSL_CTX_set1_group_ids. Because `SslRef::curve()` returns the public SSL_GROUP id - // as opposed to the internal NID, but `SslContextBuilder::set_curves()` requires the internal - // NID, we need this mapping in place to avoid breaking changes to the public API. Once the - // underlying boringssl version is upgraded, this should be removed in favor of the new - // SSL_CTX_set1_group_ids API. - pub fn nid(&self) -> Option { - match self.0 { - ffi::SSL_GROUP_SECP224R1 => Some(ffi::NID_secp224r1), - ffi::SSL_GROUP_SECP256R1 => Some(ffi::NID_X9_62_prime256v1), - ffi::SSL_GROUP_SECP384R1 => Some(ffi::NID_secp384r1), - ffi::SSL_GROUP_SECP521R1 => Some(ffi::NID_secp521r1), - ffi::SSL_GROUP_X25519 => Some(ffi::NID_X25519), - ffi::SSL_GROUP_X25519_KYBER768_DRAFT00 => Some(ffi::NID_X25519Kyber768Draft00), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_X25519_KYBER768_DRAFT00_OLD => Some(ffi::NID_X25519Kyber768Draft00Old), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_X25519_KYBER512_DRAFT00 => Some(ffi::NID_X25519Kyber512Draft00), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_P256_KYBER768_DRAFT00 => Some(ffi::NID_P256Kyber768Draft00), - #[cfg(feature = "pq-experimental")] - ffi::SSL_GROUP_X25519_MLKEM768 => Some(ffi::NID_X25519MLKEM768), - _ => None, - } - .map(SslCurveNid) - } -} - /// A compliance policy. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct CompliancePolicy(ffi::ssl_compliance_policy_t); @@ -2025,24 +1945,6 @@ impl SslContextBuilder { } } - /// Sets the context's supported curves. - #[corresponds(SSL_CTX_set1_curves)] - pub fn set_curves(&mut self, curves: &[SslCurve]) -> Result<(), ErrorStack> { - let curves: Vec = curves - .iter() - .filter_map(|curve| curve.nid().map(|nid| nid.0)) - .collect(); - - unsafe { - cvt_0i(ffi::SSL_CTX_set1_curves( - self.as_ptr(), - curves.as_ptr() as *const _, - curves.len(), - )) - .map(|_| ()) - } - } - /// Sets the context's compliance policy. /// /// This feature isn't available in the certified version of BoringSSL. @@ -2887,31 +2789,6 @@ impl SslRef { } } - /// Sets the ongoing session's supported groups by their named identifiers - /// (formerly referred to as curves). - #[corresponds(SSL_set1_groups)] - pub fn set_group_nids(&mut self, group_nids: &[SslCurveNid]) -> Result<(), ErrorStack> { - unsafe { - cvt_0i(ffi::SSL_set1_curves( - self.as_ptr(), - group_nids.as_ptr() as *const _, - group_nids.len(), - )) - .map(|_| ()) - } - } - - /// Returns the [`SslCurve`] used for this `SslRef`. - #[corresponds(SSL_get_curve_id)] - #[must_use] - pub fn curve(&self) -> Option { - let curve_id = unsafe { ffi::SSL_get_curve_id(self.as_ptr()) }; - if curve_id == 0 { - return None; - } - Some(SslCurve(curve_id.into())) - } - /// Returns an `ErrorCode` value for the most recent operation on this `SslRef`. #[corresponds(SSL_get_error)] #[must_use] diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index 0a4f6243..a5d937a4 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -13,9 +13,8 @@ use crate::pkey::PKey; use crate::srtp::SrtpProfileId; use crate::ssl::test::server::Server; use crate::ssl::SslVersion; -use crate::ssl::{self, SslCurve}; use crate::ssl::{ - ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, + self, ExtensionType, ShutdownResult, ShutdownState, Ssl, SslAcceptor, SslAcceptorBuilder, SslConnector, SslContext, SslFiletype, SslMethod, SslOptions, SslStream, SslVerifyMode, }; use crate::x509::store::X509StoreBuilder; @@ -952,36 +951,6 @@ fn sni_callback_swapped_ctx() { assert!(CALLED_BACK.load(Ordering::SeqCst)); } -#[test] -fn get_curve() { - let server = Server::builder().build(); - let client = server.client_with_root_ca(); - let client_stream = client.connect(); - let curve = client_stream.ssl().curve().expect("curve"); - assert!(curve.name().is_some()); -} - -#[test] -fn get_curve_name() { - assert_eq!(SslCurve::SECP224R1.name(), Some("P-224")); - assert_eq!(SslCurve::SECP256R1.name(), Some("P-256")); - assert_eq!(SslCurve::SECP384R1.name(), Some("P-384")); - assert_eq!(SslCurve::SECP521R1.name(), Some("P-521")); - assert_eq!(SslCurve::X25519.name(), Some("X25519")); -} - -#[test] -fn set_curves() { - let mut ctx = SslContext::builder(SslMethod::tls()).unwrap(); - ctx.set_curves(&[ - SslCurve::SECP224R1, - SslCurve::SECP256R1, - SslCurve::SECP384R1, - SslCurve::X25519, - ]) - .expect("Failed to set curves"); -} - #[test] fn test_get_ciphers() { let ctx_builder = SslContext::builder(SslMethod::tls()).unwrap(); From 7078f61077d1904a513383fae9e8868db1635e86 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Mon, 29 Sep 2025 16:13:56 -0700 Subject: [PATCH 19/39] Remove outdated comments on FIPS API compatibility --- boring/src/ssl/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 574f001d..3d91ad94 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1895,10 +1895,6 @@ impl SslContextBuilder { } /// Configures whether ClientHello extensions should be permuted. - /// - /// Note: This is gated to non-fips because the fips feature builds with a separate - /// version of BoringSSL which doesn't yet include these APIs. - /// Once the submoduled fips commit is upgraded, these gates can be removed. #[corresponds(SSL_CTX_set_permute_extensions)] pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_CTX_set_permute_extensions(self.as_ptr(), enabled as _) } @@ -2938,10 +2934,6 @@ impl SslRef { /// Configures whether ClientHello extensions should be permuted. #[corresponds(SSL_set_permute_extensions)] - /// - /// Note: This is gated to non-fips because the fips feature builds with a separate - /// version of BoringSSL which doesn't yet include these APIs. - /// Once the submoduled fips commit is upgraded, these gates can be removed. pub fn set_permute_extensions(&mut self, enabled: bool) { unsafe { ffi::SSL_set_permute_extensions(self.as_ptr(), enabled as _) } } From 1c51c7ee3bbee43f75f9b449c45d0b701a1522a8 Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 30 Sep 2025 07:51:37 -0700 Subject: [PATCH 20/39] Add back the `curve()` method on `SslRef` Instead of returning an `SslCurve`, just return the `u16` returned by BoringSSL. --- boring/src/ssl/mod.rs | 11 +++++++++++ boring/src/ssl/test/mod.rs | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 3d91ad94..b14e3530 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2785,6 +2785,17 @@ impl SslRef { } } + /// Returns the curve ID (aka group ID) used for this `SslRef`. + #[corresponds(SSL_get_curve_id)] + #[must_use] + pub fn curve(&self) -> Option { + let curve_id = unsafe { ffi::SSL_get_curve_id(self.as_ptr()) }; + if curve_id == 0 { + return None; + } + Some(curve_id) + } + /// Returns an `ErrorCode` value for the most recent operation on this `SslRef`. #[corresponds(SSL_get_error)] #[must_use] diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index a5d937a4..e779c040 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -951,6 +951,15 @@ fn sni_callback_swapped_ctx() { assert!(CALLED_BACK.load(Ordering::SeqCst)); } +#[test] +fn get_curve() { + let server = Server::builder().build(); + let client = server.client_with_root_ca(); + let client_stream = client.connect(); + let curve = client_stream.ssl().curve(); + assert!(curve.is_some()); +} + #[test] fn test_get_ciphers() { let ctx_builder = SslContext::builder(SslMethod::tls()).unwrap(); From 4ce1308e1c0f0c8cce2a5c52a2c50b6c7251c3e1 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 26 Sep 2025 18:09:24 +0100 Subject: [PATCH 21/39] Make rpk feature flag additive --- boring/src/ssl/mod.rs | 46 ++++++++++++++----------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index b14e3530..5d12e686 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -858,7 +858,11 @@ impl SslContextBuilder { init(); let ctx = cvt_p(ffi::SSL_CTX_new(SslMethod::tls_with_buffer().as_ptr()))?; - Ok(SslContextBuilder::from_ptr(ctx, true)) + let mut builder = SslContextBuilder::from_ptr(ctx); + builder.is_rpk = true; + builder.set_ex_data(*RPK_FLAG_INDEX, true); + + Ok(builder) } } @@ -897,48 +901,28 @@ impl SslContextBuilder { unsafe { init(); let ctx = cvt_p(ffi::SSL_CTX_new(method.as_ptr()))?; - - #[cfg(feature = "rpk")] - { - Ok(SslContextBuilder::from_ptr(ctx, false)) - } - - #[cfg(not(feature = "rpk"))] - { - Ok(SslContextBuilder::from_ptr(ctx)) - } + Ok(SslContextBuilder::from_ptr(ctx)) } } /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. /// - /// # Safety - /// - /// The caller must ensure that the pointer is valid and uniquely owned by the builder. - #[cfg(feature = "rpk")] - pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX, is_rpk: bool) -> SslContextBuilder { - let ctx = SslContext::from_ptr(ctx); - let mut builder = SslContextBuilder { - ctx, - is_rpk, - has_shared_cert_store: false, - }; - - builder.set_ex_data(*RPK_FLAG_INDEX, is_rpk); - - builder - } - - /// Creates an `SslContextBuilder` from a pointer to a raw OpenSSL value. + #[cfg_attr( + feature = "rpk", + doc = "Keeps previous RPK state. Use `new_rpk()` to enable RPK." + )] /// /// # Safety /// /// The caller must ensure that the pointer is valid and uniquely owned by the builder. - #[cfg(not(feature = "rpk"))] + /// The context must own its cert store exclusively. pub unsafe fn from_ptr(ctx: *mut ffi::SSL_CTX) -> SslContextBuilder { + let ctx = SslContext::from_ptr(ctx); SslContextBuilder { - ctx: SslContext::from_ptr(ctx), + #[cfg(feature = "rpk")] + is_rpk: ctx.is_rpk(), has_shared_cert_store: false, + ctx, } } From b3521e55231928745d592aab34a6ca047d9c1032 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini Date: Tue, 30 Sep 2025 16:29:18 +0100 Subject: [PATCH 22/39] Add SslRef::curve_name() --- boring/src/ssl/mod.rs | 16 ++++++++++++++++ boring/src/ssl/test/mod.rs | 2 ++ 2 files changed, 18 insertions(+) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 5d12e686..c8e45bc7 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -2780,6 +2780,22 @@ impl SslRef { Some(curve_id) } + /// Returns the curve name used for this `SslRef`. + #[corresponds(SSL_get_curve_name)] + #[must_use] + pub fn curve_name(&self) -> Option<&'static str> { + let curve_id = self.curve()?; + + unsafe { + let ptr = ffi::SSL_get_curve_name(curve_id); + if ptr.is_null() { + return None; + } + + CStr::from_ptr(ptr).to_str().ok() + } + } + /// Returns an `ErrorCode` value for the most recent operation on this `SslRef`. #[corresponds(SSL_get_error)] #[must_use] diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index e779c040..f4ce2102 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -958,6 +958,8 @@ fn get_curve() { let client_stream = client.connect(); let curve = client_stream.ssl().curve(); assert!(curve.is_some()); + let curve_name = client_stream.ssl().curve_name(); + assert!(curve_name.is_some()); } #[test] From c49282f112c2750d8740b7baed9c00a14dc8f8c3 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Sat, 8 Mar 2025 10:35:46 -0800 Subject: [PATCH 23/39] Add set_ticket_key_callback (SSL_CTX_set_tlsext_ticket_key_cb) Add a wrapper for the `SSL_CTX_set_tlsext_ticket_key_cb`, which allows consumers to configure the EVP_CIPHER_CTX and HMAC_CTX used for encrypting/decrypting session tickets. See https://docs.openssl.org/1.0.2/man3/SSL_CTX_set_tlsext_ticket_key_cb/ for more details. --- boring/src/ssl/callbacks.rs | 41 ++++++ boring/src/ssl/mod.rs | 81 +++++++++++ boring/src/ssl/test/mod.rs | 1 + boring/src/ssl/test/session_resumption.rs | 159 ++++++++++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 boring/src/ssl/test/session_resumption.rs diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index f618e591..eca7756f 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -8,6 +8,7 @@ use super::{ }; use crate::error::ErrorStack; use crate::ffi; +use crate::ssl::TicketKeyCallbackResult; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; @@ -269,6 +270,46 @@ where } } +pub(super) unsafe extern "C" fn raw_ticket_key( + ssl: *mut ffi::SSL, + key_name: *mut u8, + iv: *mut u8, + evp_ctx: *mut ffi::EVP_CIPHER_CTX, + hmac_ctx: *mut ffi::HMAC_CTX, + encrypt: c_int, +) -> c_int +where + F: Fn( + &SslRef, + &mut [u8; 16], + *mut u8, + *mut ffi::EVP_CIPHER_CTX, + *mut ffi::HMAC_CTX, + bool, + ) -> TicketKeyCallbackResult + + 'static + + Sync + + Send, +{ + // SAFETY: boring provides valid inputs. + let ssl = unsafe { SslRef::from_ptr_mut(ssl) }; + + let ssl_context = ssl.ssl_context().to_owned(); + let callback = ssl_context + .ex_data::(SslContext::cached_ex_index::()) + .expect("expected session resumption callback"); + + // Safety: the callback guarantees that key_name is 16 bytes + let key_name = + unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; + let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); + + // When encrypting a new ticket, encrypt will be one. + let encrypt = encrypt == 1; + + callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() +} + pub(super) unsafe extern "C" fn raw_alpn_select( ssl: *mut ffi::SSL, out: *mut *const c_uchar, diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index c8e45bc7..586a81dc 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -804,6 +804,50 @@ pub enum SslInfoCallbackValue { Alert(SslInfoCallbackAlert), } +/// Ticket key callback status. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum TicketKeyCallbackResult { + /// Abort the handshake. + Error, + + /// The peer supplied session ticket was not recognized. Continue with a full handshake. + /// + /// # Note + /// + /// This is a decryption specific status code. + DecryptTicketUnrecognized, + + /// Resumption callback was successful. + /// + /// When in decryption mode, attempt an abbreviated handshake via session resumption. When in + /// encryption mode, provide a new ticket to the client. + Success, + + /// Resumption callback was successful. Attempt an abbreviated handshake, and additionally + /// provide new session tickets to the peer. + /// + /// Session resumption short-circuits some security checks of a full-handshake, in exchange for + /// potential performance gains. For this reason, a session ticket should only be valid for a + /// limited time. Providing the peer with renewed session tickets allows them to continue + /// session resumption with the new tickets. + /// + /// # Note + /// + /// This is a decryption specific status code. + DecryptSuccessRenew, +} + +impl From for c_int { + fn from(value: TicketKeyCallbackResult) -> Self { + match value { + TicketKeyCallbackResult::Error => -1, + TicketKeyCallbackResult::DecryptTicketUnrecognized => 0, + TicketKeyCallbackResult::Success => 1, + TicketKeyCallbackResult::DecryptSuccessRenew => 2, + } + } +} + #[derive(Hash, Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Debug)] pub struct SslInfoCallbackAlert(c_int); @@ -1080,6 +1124,43 @@ impl SslContextBuilder { } } + /// Configures a custom session ticket key callback for session resumption. + /// + /// Session Resumption uses the security context (aka. session tickets) of a previous + /// connection to establish a new connection via an abbreviated handshake. Skipping portions of + /// a handshake can potentially yield performance gains. + /// + /// An attacker that compromises a server's session ticket key can impersonate the server and, + /// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that + /// ticket key. Thus ticket keys must be regularly rotated for forward secrecy. + /// + /// # Panics + /// + /// This method panics if this `Ssl` is associated with a RPK context. + #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] + pub fn set_ticket_key_callback(&mut self, callback: F) + where + F: Fn( + &SslRef, + &mut [u8; 16], + *mut u8, + *mut ffi::EVP_CIPHER_CTX, + *mut ffi::HMAC_CTX, + bool, + ) -> TicketKeyCallbackResult + + 'static + + Sync + + Send, + { + #[cfg(feature = "rpk")] + assert!(!self.is_rpk, "This API is not supported for RPK"); + + unsafe { + self.replace_ex_data(SslContext::cached_ex_index::(), callback); + ffi::SSL_CTX_set_tlsext_ticket_key_cb(self.as_ptr(), Some(raw_ticket_key::)) + }; + } + /// Sets the certificate verification depth. /// /// If the peer's certificate chain is longer than this value, verification will fail. diff --git a/boring/src/ssl/test/mod.rs b/boring/src/ssl/test/mod.rs index f4ce2102..aded182d 100644 --- a/boring/src/ssl/test/mod.rs +++ b/boring/src/ssl/test/mod.rs @@ -30,6 +30,7 @@ mod ech; mod private_key_method; mod server; mod session; +mod session_resumption; mod verify; static ROOT_CERT: &[u8] = include_bytes!("../../../test/root-ca.pem"); diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs new file mode 100644 index 00000000..c7556dfe --- /dev/null +++ b/boring/src/ssl/test/session_resumption.rs @@ -0,0 +1,159 @@ +use super::server::Server; +use crate::ssl::test::MessageDigest; +use crate::ssl::SslRef; +use crate::ssl::SslSession; +use crate::ssl::SslSessionCacheMode; +use crate::ssl::TicketKeyCallbackResult; +use crate::symm::Cipher; +use std::ffi::c_void; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::OnceLock; + +static CUSTOM_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static CUSTOM_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); + +#[test] +fn resume_session() { + static SESSION_TICKET: OnceLock> = OnceLock::new(); + + let mut server = Server::builder(); + server.expected_connections_count(2); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + + // Retrieve the session ticket + let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + assert!(ssl_stream_2.ssl().session_reused()); +} + +#[test] +fn custom_callback() { + static SESSION_TICKET: OnceLock> = OnceLock::new(); + + let mut server = Server::builder(); + server.expected_connections_count(2); + server + .ctx() + .set_ticket_key_callback(test_tickey_key_callback); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + + // Retrieve the session ticket + let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + assert!(ssl_stream_2.ssl().session_reused()); + assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); +} + +// Custom callback to encrypt and decrypt session tickets +fn test_tickey_key_callback( + _ssl: &SslRef, + _key_name: &mut [u8; 16], + _iv: *mut u8, + evp_ctx: *mut ffi::EVP_CIPHER_CTX, + hmac_ctx: *mut ffi::HMAC_CTX, + encrypt: bool, +) -> TicketKeyCallbackResult { + // These should only be used for testing purposes. + const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; + const TEST_HMAC_KEY: [u8; 32] = [3; 32]; + + let digest = MessageDigest::sha256(); + let cipher = Cipher::aes_128_cbc(); + + if encrypt { + CUSTOM_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. + let ret = unsafe { + ffi::EVP_EncryptInit_ex( + evp_ctx, + cipher.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + TEST_AES_128_CBC_KEY.as_ptr(), + TEST_CBC_IV.as_ptr(), + ) + }; + assert!(ret == 1); + + // Set the hmac context. + let ret = unsafe { + ffi::HMAC_Init_ex( + hmac_ctx, + TEST_HMAC_KEY.as_ptr() as *const c_void, + TEST_HMAC_KEY.len(), + digest.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ) + }; + assert!(ret == 1); + } else { + CUSTOM_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + let ret = unsafe { + ffi::EVP_DecryptInit_ex( + evp_ctx, + cipher.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + TEST_AES_128_CBC_KEY.as_ptr(), + TEST_CBC_IV.as_ptr(), + ) + }; + assert!(ret == 1); + + // Set the hmac context. + let ret = unsafe { + ffi::HMAC_Init_ex( + hmac_ctx, + TEST_HMAC_KEY.as_ptr() as *const c_void, + TEST_HMAC_KEY.len(), + digest.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ) + }; + assert!(ret == 1); + } + + TicketKeyCallbackResult::Success +} From ea1d120912468c4fa4cb2714572deed2ef6d6212 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Mon, 10 Mar 2025 22:30:25 -0700 Subject: [PATCH 24/39] pr comments: safety, receive multiple nst, return status refactor --- boring/src/ssl/callbacks.rs | 2 +- boring/src/ssl/mod.rs | 8 +++++--- boring/src/ssl/test/session_resumption.rs | 16 ++++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index eca7756f..a8e3d5ca 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -299,7 +299,7 @@ where .ex_data::(SslContext::cached_ex_index::()) .expect("expected session resumption callback"); - // Safety: the callback guarantees that key_name is 16 bytes + // SAFETY: the callback guarantees that key_name is 16 bytes let key_name = unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 586a81dc..59f68acd 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -810,12 +810,14 @@ pub enum TicketKeyCallbackResult { /// Abort the handshake. Error, - /// The peer supplied session ticket was not recognized. Continue with a full handshake. + /// Continue with a full handshake. + /// + /// The peer supplied session ticket was not recognized. /// /// # Note /// /// This is a decryption specific status code. - DecryptTicketUnrecognized, + Noop, /// Resumption callback was successful. /// @@ -841,7 +843,7 @@ impl From for c_int { fn from(value: TicketKeyCallbackResult) -> Self { match value { TicketKeyCallbackResult::Error => -1, - TicketKeyCallbackResult::DecryptTicketUnrecognized => 0, + TicketKeyCallbackResult::Noop => 0, TicketKeyCallbackResult::Success => 1, TicketKeyCallbackResult::DecryptSuccessRenew => 2, } diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index c7556dfe..3492fff8 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -15,6 +15,7 @@ static CUSTOM_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); #[test] fn resume_session() { static SESSION_TICKET: OnceLock> = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); server.expected_connections_count(2); @@ -25,12 +26,17 @@ fn resume_session() { .ctx() .set_session_cache_mode(SslSessionCacheMode::CLIENT); client.ctx().set_new_session_callback(|_, session| { - let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + if SESSION_TICKET.get().is_none() { + SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); + } }); let ssl_stream = client.connect(); assert!(!ssl_stream.ssl().session_reused()); assert!(SESSION_TICKET.get().is_some()); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); @@ -47,6 +53,7 @@ fn resume_session() { #[test] fn custom_callback() { static SESSION_TICKET: OnceLock> = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); server.expected_connections_count(2); @@ -60,7 +67,11 @@ fn custom_callback() { .ctx() .set_session_cache_mode(SslSessionCacheMode::CLIENT); client.ctx().set_new_session_callback(|_, session| { - let _can_receive_multiple_tickets = SESSION_TICKET.set(session.to_der().unwrap()); + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + if SESSION_TICKET.get().is_none() { + SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); + } }); let ssl_stream = client.connect(); @@ -68,6 +79,7 @@ fn custom_callback() { assert!(SESSION_TICKET.get().is_some()); assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); From ae783f827344437bcc23453f2e97d94ecfb49d5c Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 11 Mar 2025 12:10:21 -0700 Subject: [PATCH 25/39] add test case for TicketKeyCallbackResult::Noop --- boring/src/ssl/test/session_resumption.rs | 125 +++++++++++++++++++--- 1 file changed, 113 insertions(+), 12 deletions(-) diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index 3492fff8..638461a7 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -9,8 +9,10 @@ use std::ffi::c_void; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::OnceLock; -static CUSTOM_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); -static CUSTOM_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static SUCCESS_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static SUCCESS_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static NOOP_ENCRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); +static NOOP_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); #[test] fn resume_session() { @@ -51,7 +53,7 @@ fn resume_session() { } #[test] -fn custom_callback() { +fn custom_callback_success() { static SESSION_TICKET: OnceLock> = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); @@ -59,7 +61,7 @@ fn custom_callback() { server.expected_connections_count(2); server .ctx() - .set_ticket_key_callback(test_tickey_key_callback); + .set_ticket_key_callback(test_success_tickey_key_callback); let server = server.build(); let mut client = server.client(); @@ -77,8 +79,8 @@ fn custom_callback() { assert!(!ssl_stream.ssl().session_reused()); assert!(SESSION_TICKET.get().is_some()); - assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); - assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket @@ -91,12 +93,59 @@ fn custom_callback() { let ssl_stream_2 = ssl_builder.connect(); assert!(ssl_stream_2.ssl().session_reused()); - assert_eq!(CUSTOM_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); - assert_eq!(CUSTOM_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); + assert_eq!(SUCCESS_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(SUCCESS_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); } -// Custom callback to encrypt and decrypt session tickets -fn test_tickey_key_callback( +#[test] +fn custom_callback_unrecognized_decryption_ticket() { + static SESSION_TICKET: OnceLock> = OnceLock::new(); + static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); + + let mut server = Server::builder(); + server.expected_connections_count(2); + server + .ctx() + .set_ticket_key_callback(test_noop_tickey_key_callback); + let server = server.build(); + + let mut client = server.client(); + client + .ctx() + .set_session_cache_mode(SslSessionCacheMode::CLIENT); + client.ctx().set_new_session_callback(|_, session| { + NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); + // The server sends multiple session tickets but we only care to retrieve one. + if SESSION_TICKET.get().is_none() { + SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); + } + }); + let ssl_stream = client.connect(); + + assert!(!ssl_stream.ssl().session_reused()); + assert!(SESSION_TICKET.get().is_some()); + assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 2); + assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 0); + assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); + + // Retrieve the session ticket + let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + + // Attempt to resume the connection using the session ticket + let client_2 = server.client(); + let mut ssl_builder = client_2.build().builder(); + unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + let ssl_stream_2 = ssl_builder.connect(); + + // Second connection was NOT resumed due to TicketKeyCallbackResult::Noop on decryption + assert!(!ssl_stream_2.ssl().session_reused()); + assert_eq!(NOOP_ENCRYPTION_CALLED_BACK.load(Ordering::SeqCst), 4); + assert_eq!(NOOP_DECRYPTION_CALLED_BACK.load(Ordering::SeqCst), 1); +} + +// Successfully return a session ticket in encryption mode but return a +// TicketKeyCallbackResult::Noop in decryption mode. +fn test_noop_tickey_key_callback( _ssl: &SslRef, _key_name: &mut [u8; 16], _iv: *mut u8, @@ -113,7 +162,59 @@ fn test_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { - CUSTOM_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. + let ret = unsafe { + ffi::EVP_EncryptInit_ex( + evp_ctx, + cipher.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + TEST_AES_128_CBC_KEY.as_ptr(), + TEST_CBC_IV.as_ptr(), + ) + }; + assert!(ret == 1); + + // Set the hmac context. + let ret = unsafe { + ffi::HMAC_Init_ex( + hmac_ctx, + TEST_HMAC_KEY.as_ptr() as *const c_void, + TEST_HMAC_KEY.len(), + digest.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + ) + }; + assert!(ret == 1); + + TicketKeyCallbackResult::Success + } else { + NOOP_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + TicketKeyCallbackResult::Noop + } +} + +// Custom callback to encrypt and decrypt session tickets +fn test_success_tickey_key_callback( + _ssl: &SslRef, + _key_name: &mut [u8; 16], + _iv: *mut u8, + evp_ctx: *mut ffi::EVP_CIPHER_CTX, + hmac_ctx: *mut ffi::HMAC_CTX, + encrypt: bool, +) -> TicketKeyCallbackResult { + // These should only be used for testing purposes. + const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; + const TEST_HMAC_KEY: [u8; 32] = [3; 32]; + + let digest = MessageDigest::sha256(); + let cipher = Cipher::aes_128_cbc(); + + if encrypt { + SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the encryption context. let ret = unsafe { ffi::EVP_EncryptInit_ex( @@ -140,7 +241,7 @@ fn test_tickey_key_callback( }; assert!(ret == 1); } else { - CUSTOM_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); let ret = unsafe { ffi::EVP_DecryptInit_ex( evp_ctx, From f526b57daa1ddad686e83ca5ac91ed7799767599 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 1 Apr 2025 12:19:23 -0700 Subject: [PATCH 26/39] update documentation --- boring/src/ssl/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 59f68acd..c19c7381 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -812,11 +812,12 @@ pub enum TicketKeyCallbackResult { /// Continue with a full handshake. /// - /// The peer supplied session ticket was not recognized. + /// When in decryption mode, this indicates that the peer supplied session ticket was not + /// recognized. When in encryption mode, this instructs boring to not send a session ticket. /// /// # Note /// - /// This is a decryption specific status code. + /// This is a decryption specific status code when using the submoduled BoringSSL. Noop, /// Resumption callback was successful. From ba85fbb7ad6d0e095e80aee40b7740b904f689ed Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Wed, 2 Apr 2025 09:46:50 -0700 Subject: [PATCH 27/39] simplify tests --- boring/src/ssl/test/session_resumption.rs | 24 +++++++++-------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index 638461a7..df9c62b7 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -16,7 +16,7 @@ static NOOP_DECRYPTION_CALLED_BACK: AtomicU8 = AtomicU8::new(0); #[test] fn resume_session() { - static SESSION_TICKET: OnceLock> = OnceLock::new(); + static SESSION_TICKET: OnceLock = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); @@ -30,9 +30,7 @@ fn resume_session() { client.ctx().set_new_session_callback(|_, session| { NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); // The server sends multiple session tickets but we only care to retrieve one. - if SESSION_TICKET.get().is_none() { - SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); - } + let _ = SESSION_TICKET.set(session); }); let ssl_stream = client.connect(); @@ -41,7 +39,7 @@ fn resume_session() { assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket - let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + let session_ticket = SESSION_TICKET.get().unwrap(); // Attempt to resume the connection using the session ticket let client_2 = server.client(); @@ -54,7 +52,7 @@ fn resume_session() { #[test] fn custom_callback_success() { - static SESSION_TICKET: OnceLock> = OnceLock::new(); + static SESSION_TICKET: OnceLock = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); @@ -71,9 +69,7 @@ fn custom_callback_success() { client.ctx().set_new_session_callback(|_, session| { NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); // The server sends multiple session tickets but we only care to retrieve one. - if SESSION_TICKET.get().is_none() { - SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); - } + let _ = SESSION_TICKET.set(session); }); let ssl_stream = client.connect(); @@ -84,7 +80,7 @@ fn custom_callback_success() { assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket - let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + let session_ticket = SESSION_TICKET.get().unwrap(); // Attempt to resume the connection using the session ticket let client_2 = server.client(); @@ -99,7 +95,7 @@ fn custom_callback_success() { #[test] fn custom_callback_unrecognized_decryption_ticket() { - static SESSION_TICKET: OnceLock> = OnceLock::new(); + static SESSION_TICKET: OnceLock = OnceLock::new(); static NST_RECIEVED_COUNT: AtomicU8 = AtomicU8::new(0); let mut server = Server::builder(); @@ -116,9 +112,7 @@ fn custom_callback_unrecognized_decryption_ticket() { client.ctx().set_new_session_callback(|_, session| { NST_RECIEVED_COUNT.fetch_add(1, Ordering::SeqCst); // The server sends multiple session tickets but we only care to retrieve one. - if SESSION_TICKET.get().is_none() { - SESSION_TICKET.set(session.to_der().unwrap()).unwrap(); - } + let _ = SESSION_TICKET.set(session); }); let ssl_stream = client.connect(); @@ -129,7 +123,7 @@ fn custom_callback_unrecognized_decryption_ticket() { assert_eq!(NST_RECIEVED_COUNT.load(Ordering::SeqCst), 2); // Retrieve the session ticket - let session_ticket = SslSession::from_der(SESSION_TICKET.get().unwrap()).unwrap(); + let session_ticket = SESSION_TICKET.get().unwrap(); // Attempt to resume the connection using the session ticket let client_2 = server.client(); From b9af0ef176bad78dc9e02159b29e5cf1710e5fe0 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Wed, 2 Apr 2025 10:40:52 -0700 Subject: [PATCH 28/39] clippy --- boring/src/ssl/test/session_resumption.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index df9c62b7..e604d410 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -44,7 +44,7 @@ fn resume_session() { // Attempt to resume the connection using the session ticket let client_2 = server.client(); let mut ssl_builder = client_2.build().builder(); - unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; let ssl_stream_2 = ssl_builder.connect(); assert!(ssl_stream_2.ssl().session_reused()); @@ -85,7 +85,7 @@ fn custom_callback_success() { // Attempt to resume the connection using the session ticket let client_2 = server.client(); let mut ssl_builder = client_2.build().builder(); - unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; let ssl_stream_2 = ssl_builder.connect(); assert!(ssl_stream_2.ssl().session_reused()); @@ -128,7 +128,7 @@ fn custom_callback_unrecognized_decryption_ticket() { // Attempt to resume the connection using the session ticket let client_2 = server.client(); let mut ssl_builder = client_2.build().builder(); - unsafe { ssl_builder.ssl().set_session(&session_ticket).unwrap() }; + unsafe { ssl_builder.ssl().set_session(session_ticket).unwrap() }; let ssl_stream_2 = ssl_builder.connect(); // Second connection was NOT resumed due to TicketKeyCallbackResult::Noop on decryption From 5cb35db98924f8222e2dabca27a882727e66fa44 Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Mon, 29 Sep 2025 14:15:41 -0700 Subject: [PATCH 29/39] initialize key_name and iv. mark fn as _unsafe to allow for future changes to the api --- boring/src/ssl/callbacks.rs | 16 ++++++++++- boring/src/ssl/mod.rs | 11 +++++-- boring/src/ssl/test/session_resumption.rs | 35 +++++++++++++++-------- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index a8e3d5ca..d1f42f77 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -282,7 +282,7 @@ where F: Fn( &SslRef, &mut [u8; 16], - *mut u8, + &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], *mut ffi::EVP_CIPHER_CTX, *mut ffi::HMAC_CTX, bool, @@ -304,9 +304,23 @@ where unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); + // SAFETY: the callback provides 16 bytes iv + // + // https://github.com/google/boringssl/blob/main/ssl/ssl_session.cc#L331 + let iv = unsafe { core::slice::from_raw_parts_mut(iv, ffi::EVP_MAX_IV_LENGTH as usize) }; + let iv = <&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize]>::try_from(iv) + .expect("boring provides a 16-byte iv"); + // When encrypting a new ticket, encrypt will be one. let encrypt = encrypt == 1; + // Zero-initialize the key_name and iv, since the application is expected to populate these + // fields in the encrypt mode. + if encrypt { + unsafe { ptr::write(key_name, [0; 16]) }; + unsafe { ptr::write(iv, [0; ffi::EVP_MAX_IV_LENGTH as usize]) }; + } + callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index c19c7381..147ce469 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -1140,13 +1140,20 @@ impl SslContextBuilder { /// # Panics /// /// This method panics if this `Ssl` is associated with a RPK context. + /// + /// # Safety + /// + /// The application is responsible for correctly setting the key_name, iv, encryption context + /// and hmac context. See the [`SSL_CTX_set_tlsext_ticket_key_cb`] docs for additional info. + /// + /// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] - pub fn set_ticket_key_callback(&mut self, callback: F) + pub unsafe fn set_ticket_key_callback_unsafe(&mut self, callback: F) where F: Fn( &SslRef, &mut [u8; 16], - *mut u8, + &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], *mut ffi::EVP_CIPHER_CTX, *mut ffi::HMAC_CTX, bool, diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index e604d410..c4b6c2d4 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -57,9 +57,11 @@ fn custom_callback_success() { let mut server = Server::builder(); server.expected_connections_count(2); - server - .ctx() - .set_ticket_key_callback(test_success_tickey_key_callback); + unsafe { + server + .ctx() + .set_ticket_key_callback_unsafe(test_success_tickey_key_callback) + }; let server = server.build(); let mut client = server.client(); @@ -100,9 +102,11 @@ fn custom_callback_unrecognized_decryption_ticket() { let mut server = Server::builder(); server.expected_connections_count(2); - server - .ctx() - .set_ticket_key_callback(test_noop_tickey_key_callback); + unsafe { + server + .ctx() + .set_ticket_key_callback_unsafe(test_noop_tickey_key_callback) + }; let server = server.build(); let mut client = server.client(); @@ -141,14 +145,14 @@ fn custom_callback_unrecognized_decryption_ticket() { // TicketKeyCallbackResult::Noop in decryption mode. fn test_noop_tickey_key_callback( _ssl: &SslRef, - _key_name: &mut [u8; 16], - _iv: *mut u8, + key_name: &mut [u8; 16], + iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], evp_ctx: *mut ffi::EVP_CIPHER_CTX, hmac_ctx: *mut ffi::HMAC_CTX, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. - const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -156,6 +160,9 @@ fn test_noop_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the encryption context. let ret = unsafe { @@ -193,14 +200,14 @@ fn test_noop_tickey_key_callback( // Custom callback to encrypt and decrypt session tickets fn test_success_tickey_key_callback( _ssl: &SslRef, - _key_name: &mut [u8; 16], - _iv: *mut u8, + key_name: &mut [u8; 16], + iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], evp_ctx: *mut ffi::EVP_CIPHER_CTX, hmac_ctx: *mut ffi::HMAC_CTX, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. - const TEST_CBC_IV: [u8; 16] = [1; 16]; + const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -208,6 +215,9 @@ fn test_success_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the encryption context. let ret = unsafe { @@ -236,6 +246,7 @@ fn test_success_tickey_key_callback( assert!(ret == 1); } else { SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the decryption context. let ret = unsafe { ffi::EVP_DecryptInit_ex( evp_ctx, From ac1d71cb54d001b8035f4224ee61c097f8688432 Mon Sep 17 00:00:00 2001 From: Kornel Date: Tue, 30 Sep 2025 15:06:11 +0100 Subject: [PATCH 30/39] Use MaybeUninit for raw_ticket_key key/iv --- boring/src/ssl/callbacks.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index d1f42f77..958524c8 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -12,9 +12,9 @@ use crate::ssl::TicketKeyCallbackResult; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; -use libc::c_char; -use libc::{c_int, c_uchar, c_uint, c_void}; +use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; +use std::mem::MaybeUninit; use std::ptr; use std::slice; use std::str; @@ -270,6 +270,11 @@ where } } +unsafe fn to_uninit<'a, T: 'a>(ptr: *mut T) -> &'a mut MaybeUninit { + assert!(!ptr.is_null()); + unsafe { &mut *ptr.cast::>() } +} + pub(super) unsafe extern "C" fn raw_ticket_key( ssl: *mut ffi::SSL, key_name: *mut u8, @@ -301,15 +306,12 @@ where // SAFETY: the callback guarantees that key_name is 16 bytes let key_name = - unsafe { slice::from_raw_parts_mut(key_name, ffi::SSL_TICKET_KEY_NAME_LEN as usize) }; - let key_name = <&mut [u8; 16]>::try_from(key_name).expect("boring provides a 16-byte key name"); + unsafe { to_uninit(key_name.cast::<[u8; ffi::SSL_TICKET_KEY_NAME_LEN as usize]>()) }; // SAFETY: the callback provides 16 bytes iv // // https://github.com/google/boringssl/blob/main/ssl/ssl_session.cc#L331 - let iv = unsafe { core::slice::from_raw_parts_mut(iv, ffi::EVP_MAX_IV_LENGTH as usize) }; - let iv = <&mut [u8; ffi::EVP_MAX_IV_LENGTH as usize]>::try_from(iv) - .expect("boring provides a 16-byte iv"); + let iv = unsafe { to_uninit(iv.cast::<[u8; ffi::EVP_MAX_IV_LENGTH as usize]>()) }; // When encrypting a new ticket, encrypt will be one. let encrypt = encrypt == 1; @@ -317,9 +319,11 @@ where // Zero-initialize the key_name and iv, since the application is expected to populate these // fields in the encrypt mode. if encrypt { - unsafe { ptr::write(key_name, [0; 16]) }; - unsafe { ptr::write(iv, [0; ffi::EVP_MAX_IV_LENGTH as usize]) }; + *key_name = MaybeUninit::zeroed(); + *iv = MaybeUninit::zeroed(); } + let key_name = unsafe { key_name.assume_init_mut() }; + let iv = unsafe { iv.assume_init_mut() }; callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() } From ab8513ef8f2d0775cf074f23e2ec0bb03cf00bda Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 30 Sep 2025 14:44:17 -0700 Subject: [PATCH 31/39] Expose a safe Rust interface for the session resumption callback --- boring/src/hmac.rs | 38 +++++++++ boring/src/lib.rs | 1 + boring/src/ssl/callbacks.rs | 14 +++- boring/src/ssl/mod.rs | 10 ++- boring/src/ssl/test/session_resumption.rs | 95 ++++++----------------- boring/src/symm.rs | 72 +++++++++++++++++ 6 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 boring/src/hmac.rs diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs new file mode 100644 index 00000000..4a593877 --- /dev/null +++ b/boring/src/hmac.rs @@ -0,0 +1,38 @@ +use crate::cvt; +use crate::error::ErrorStack; +use crate::hash::MessageDigest; +use std::ffi::c_void; + +use foreign_types::ForeignType; + +foreign_type_and_impl_send_sync! { + type CType = ffi::HMAC_CTX; + fn drop = ffi::HMAC_CTX_free; + + pub struct HmacCtx; +} + +impl HmacCtx { + /// Configures HmacCtx to use `md` as the hash function and `key` as the key. + /// + /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex + /// + /// # Safety + /// + /// The caller must ensure HMAC_CTX has been initalized. + pub unsafe fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { + ffi::init(); + + unsafe { + cvt(ffi::HMAC_Init_ex( + self.as_ptr(), + key.as_ptr() as *const c_void, + key.len(), + md.as_ptr(), + // ENGINE api is deprecated + core::ptr::null_mut(), + )) + .map(|_| ()) + } + } +} diff --git a/boring/src/lib.rs b/boring/src/lib.rs index 77f3e726..932bdd35 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -137,6 +137,7 @@ pub mod error; pub mod ex_data; pub mod fips; pub mod hash; +pub mod hmac; pub mod hpke; pub mod memcmp; pub mod nid; diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index 958524c8..5598b6af 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -8,13 +8,15 @@ use super::{ }; use crate::error::ErrorStack; use crate::ffi; +use crate::hmac::HmacCtx; use crate::ssl::TicketKeyCallbackResult; +use crate::symm::CipherCtx; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; -use std::mem::MaybeUninit; +use std::mem::{ManuallyDrop, MaybeUninit}; use std::ptr; use std::slice; use std::str; @@ -288,8 +290,8 @@ where &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - *mut ffi::EVP_CIPHER_CTX, - *mut ffi::HMAC_CTX, + &mut CipherCtx, + &mut HmacCtx, bool, ) -> TicketKeyCallbackResult + 'static @@ -325,7 +327,11 @@ where let key_name = unsafe { key_name.assume_init_mut() }; let iv = unsafe { iv.assume_init_mut() }; - callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() + // The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL. + let mut evp_ctx = ManuallyDrop::new(unsafe { CipherCtx::from_ptr(evp_ctx) }); + let mut hmac_ctx = ManuallyDrop::new(unsafe { HmacCtx::from_ptr(hmac_ctx) }); + + callback(ssl, key_name, iv, &mut evp_ctx, &mut hmac_ctx, encrypt).into() } pub(super) unsafe extern "C" fn raw_alpn_select( diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 147ce469..1be720ac 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -81,6 +81,7 @@ use crate::dh::DhRef; use crate::ec::EcKeyRef; use crate::error::ErrorStack; use crate::ex_data::Index; +use crate::hmac::HmacCtx; use crate::nid::Nid; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; @@ -88,6 +89,7 @@ use crate::ssl::bio::BioMethod; use crate::ssl::callbacks::*; use crate::ssl::error::InnerError; use crate::stack::{Stack, StackRef, Stackable}; +use crate::symm::CipherCtx; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}; use crate::x509::verify::X509VerifyParamRef; use crate::x509::{ @@ -1137,6 +1139,8 @@ impl SslContextBuilder { /// prior to TLS 1.3, retroactively decrypt all application traffic from sessions using that /// ticket key. Thus ticket keys must be regularly rotated for forward secrecy. /// + /// CipherCtx and HmacCtx are guaranteed to be initialized. + /// /// # Panics /// /// This method panics if this `Ssl` is associated with a RPK context. @@ -1148,14 +1152,14 @@ impl SslContextBuilder { /// /// [`SSL_CTX_set_tlsext_ticket_key_cb`]: https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#SSL_CTX_set_tlsext_ticket_key_cb #[corresponds(SSL_CTX_set_tlsext_ticket_key_cb)] - pub unsafe fn set_ticket_key_callback_unsafe(&mut self, callback: F) + pub unsafe fn set_ticket_key_callback(&mut self, callback: F) where F: Fn( &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - *mut ffi::EVP_CIPHER_CTX, - *mut ffi::HMAC_CTX, + &mut CipherCtx, + &mut HmacCtx, bool, ) -> TicketKeyCallbackResult + 'static diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index c4b6c2d4..2eb62116 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -1,11 +1,12 @@ use super::server::Server; use crate::ssl::test::MessageDigest; +use crate::ssl::HmacCtx; use crate::ssl::SslRef; use crate::ssl::SslSession; use crate::ssl::SslSessionCacheMode; use crate::ssl::TicketKeyCallbackResult; use crate::symm::Cipher; -use std::ffi::c_void; +use crate::symm::CipherCtx; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::OnceLock; @@ -60,7 +61,7 @@ fn custom_callback_success() { unsafe { server .ctx() - .set_ticket_key_callback_unsafe(test_success_tickey_key_callback) + .set_ticket_key_callback(test_success_tickey_key_callback) }; let server = server.build(); @@ -105,7 +106,7 @@ fn custom_callback_unrecognized_decryption_ticket() { unsafe { server .ctx() - .set_ticket_key_callback_unsafe(test_noop_tickey_key_callback) + .set_ticket_key_callback(test_noop_tickey_key_callback) }; let server = server.build(); @@ -147,8 +148,8 @@ fn test_noop_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: *mut ffi::EVP_CIPHER_CTX, - hmac_ctx: *mut ffi::HMAC_CTX, + evp_ctx: &mut CipherCtx, + hmac_ctx: &mut HmacCtx, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. @@ -164,31 +165,16 @@ fn test_noop_tickey_key_callback( assert_eq!(iv, &[0; 16]); NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. - let ret = unsafe { - ffi::EVP_EncryptInit_ex( - evp_ctx, - cipher.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - TEST_AES_128_CBC_KEY.as_ptr(), - TEST_CBC_IV.as_ptr(), - ) + unsafe { + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap() }; - assert!(ret == 1); // Set the hmac context. - let ret = unsafe { - ffi::HMAC_Init_ex( - hmac_ctx, - TEST_HMAC_KEY.as_ptr() as *const c_void, - TEST_HMAC_KEY.len(), - digest.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - ) - }; - assert!(ret == 1); + unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; TicketKeyCallbackResult::Success } else { @@ -202,8 +188,8 @@ fn test_success_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: *mut ffi::EVP_CIPHER_CTX, - hmac_ctx: *mut ffi::HMAC_CTX, + evp_ctx: &mut CipherCtx, + hmac_ctx: &mut HmacCtx, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. @@ -219,58 +205,27 @@ fn test_success_tickey_key_callback( assert_eq!(iv, &[0; 16]); SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Set the encryption context. - let ret = unsafe { - ffi::EVP_EncryptInit_ex( - evp_ctx, - cipher.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - TEST_AES_128_CBC_KEY.as_ptr(), - TEST_CBC_IV.as_ptr(), - ) + unsafe { + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap() }; - assert!(ret == 1); // Set the hmac context. - let ret = unsafe { - ffi::HMAC_Init_ex( - hmac_ctx, - TEST_HMAC_KEY.as_ptr() as *const c_void, - TEST_HMAC_KEY.len(), - digest.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - ) - }; - assert!(ret == 1); + unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; } else { SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); // Set the decryption context. - let ret = unsafe { - ffi::EVP_DecryptInit_ex( - evp_ctx, - cipher.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - TEST_AES_128_CBC_KEY.as_ptr(), - TEST_CBC_IV.as_ptr(), - ) + unsafe { + evp_ctx + .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap() }; - assert!(ret == 1); // Set the hmac context. - let ret = unsafe { - ffi::HMAC_Init_ex( - hmac_ctx, - TEST_HMAC_KEY.as_ptr() as *const c_void, - TEST_HMAC_KEY.len(), - digest.as_ptr(), - // ENGINE api is deprecated - core::ptr::null_mut(), - ) - }; - assert!(ret == 1); + unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; } TicketKeyCallbackResult::Success diff --git a/boring/src/symm.rs b/boring/src/symm.rs index fff8a4a1..831430f5 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -53,6 +53,7 @@ //! ``` use crate::ffi; +use foreign_types::ForeignType; use libc::{c_int, c_uint}; use openssl_macros::corresponds; use std::cmp; @@ -68,6 +69,77 @@ pub enum Mode { Decrypt, } +foreign_type_and_impl_send_sync! { + type CType = ffi::EVP_CIPHER_CTX; + fn drop = ffi::EVP_CIPHER_CTX_free; + + pub struct CipherCtx; +} + +impl CipherCtx { + /// Configures CipherCtx for a fresh encryption operation using `cipher`. + /// + /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex + /// + /// # Safety + /// + /// The caller must ensure EVP_CIPHER_CTX has been initalized. + /// + /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the + /// chosen Cipher. + pub unsafe fn init_encrypt( + &mut self, + cipher: &Cipher, + key: &[u8], + iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize], + ) -> Result<(), ErrorStack> { + ffi::init(); + + unsafe { + cvt(ffi::EVP_EncryptInit_ex( + self.as_ptr(), + cipher.as_ptr(), + // ENGINE api is deprecated + ptr::null_mut(), + key.as_ptr(), + iv.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Configures CipherCtx for a fresh decryption operation using `cipher`. + /// + /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_DecryptInit_ex + /// + /// # Safety + /// + /// The caller must ensure EVP_CIPHER_CTX has been initalized. + /// + /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the + /// chosen Cipher. + pub unsafe fn init_decrypt( + &mut self, + cipher: &Cipher, + key: &[u8], + iv: &[u8; ffi::EVP_MAX_IV_LENGTH as usize], + ) -> Result<(), ErrorStack> { + ffi::init(); + + unsafe { + cvt(ffi::EVP_DecryptInit_ex( + self.as_ptr(), + cipher.as_ptr(), + // ENGINE api is deprecated + ptr::null_mut(), + key.as_ptr(), + iv.as_ptr(), + )) + .map(|_| ()) + } + } +} + /// Represents a particular cipher algorithm. /// /// See OpenSSL doc at [`EVP_EncryptInit`] for more information on each algorithms. From 8773f0e1faca1352abb1e1b421c327149f36bf86 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 03:38:59 +0100 Subject: [PATCH 32/39] Use Ref foreign type instead of forgetting --- boring/src/hmac.rs | 8 +++----- boring/src/ssl/callbacks.rs | 16 ++++++++-------- boring/src/ssl/mod.rs | 8 ++++---- boring/src/ssl/test/session_resumption.rs | 12 ++++++------ boring/src/symm.rs | 4 ++-- 5 files changed, 23 insertions(+), 25 deletions(-) diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs index 4a593877..9816fb57 100644 --- a/boring/src/hmac.rs +++ b/boring/src/hmac.rs @@ -1,9 +1,7 @@ use crate::cvt; use crate::error::ErrorStack; +use crate::foreign_types::ForeignTypeRef; use crate::hash::MessageDigest; -use std::ffi::c_void; - -use foreign_types::ForeignType; foreign_type_and_impl_send_sync! { type CType = ffi::HMAC_CTX; @@ -12,7 +10,7 @@ foreign_type_and_impl_send_sync! { pub struct HmacCtx; } -impl HmacCtx { +impl HmacCtxRef { /// Configures HmacCtx to use `md` as the hash function and `key` as the key. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex @@ -26,7 +24,7 @@ impl HmacCtx { unsafe { cvt(ffi::HMAC_Init_ex( self.as_ptr(), - key.as_ptr() as *const c_void, + key.as_ptr().cast(), key.len(), md.as_ptr(), // ENGINE api is deprecated diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index 5598b6af..ed724f79 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -8,15 +8,15 @@ use super::{ }; use crate::error::ErrorStack; use crate::ffi; -use crate::hmac::HmacCtx; +use crate::hmac::HmacCtxRef; use crate::ssl::TicketKeyCallbackResult; -use crate::symm::CipherCtx; +use crate::symm::CipherCtxRef; use crate::x509::{X509StoreContext, X509StoreContextRef}; use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; -use std::mem::{ManuallyDrop, MaybeUninit}; +use std::mem::MaybeUninit; use std::ptr; use std::slice; use std::str; @@ -290,8 +290,8 @@ where &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - &mut CipherCtx, - &mut HmacCtx, + &mut CipherCtxRef, + &mut HmacCtxRef, bool, ) -> TicketKeyCallbackResult + 'static @@ -328,10 +328,10 @@ where let iv = unsafe { iv.assume_init_mut() }; // The EVP_CIPHER_CTX and HMAC_CTX are owned by boringSSL. - let mut evp_ctx = ManuallyDrop::new(unsafe { CipherCtx::from_ptr(evp_ctx) }); - let mut hmac_ctx = ManuallyDrop::new(unsafe { HmacCtx::from_ptr(hmac_ctx) }); + let evp_ctx = unsafe { CipherCtxRef::from_ptr_mut(evp_ctx) }; + let hmac_ctx = unsafe { HmacCtxRef::from_ptr_mut(hmac_ctx) }; - callback(ssl, key_name, iv, &mut evp_ctx, &mut hmac_ctx, encrypt).into() + callback(ssl, key_name, iv, evp_ctx, hmac_ctx, encrypt).into() } pub(super) unsafe extern "C" fn raw_alpn_select( diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 1be720ac..19688c39 100644 --- a/boring/src/ssl/mod.rs +++ b/boring/src/ssl/mod.rs @@ -81,7 +81,7 @@ use crate::dh::DhRef; use crate::ec::EcKeyRef; use crate::error::ErrorStack; use crate::ex_data::Index; -use crate::hmac::HmacCtx; +use crate::hmac::HmacCtxRef; use crate::nid::Nid; use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; @@ -89,7 +89,7 @@ use crate::ssl::bio::BioMethod; use crate::ssl::callbacks::*; use crate::ssl::error::InnerError; use crate::stack::{Stack, StackRef, Stackable}; -use crate::symm::CipherCtx; +use crate::symm::CipherCtxRef; use crate::x509::store::{X509Store, X509StoreBuilder, X509StoreBuilderRef, X509StoreRef}; use crate::x509::verify::X509VerifyParamRef; use crate::x509::{ @@ -1158,8 +1158,8 @@ impl SslContextBuilder { &SslRef, &mut [u8; 16], &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - &mut CipherCtx, - &mut HmacCtx, + &mut CipherCtxRef, + &mut HmacCtxRef, bool, ) -> TicketKeyCallbackResult + 'static diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index 2eb62116..f7f481db 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -1,12 +1,12 @@ use super::server::Server; use crate::ssl::test::MessageDigest; -use crate::ssl::HmacCtx; +use crate::ssl::HmacCtxRef; use crate::ssl::SslRef; use crate::ssl::SslSession; use crate::ssl::SslSessionCacheMode; use crate::ssl::TicketKeyCallbackResult; use crate::symm::Cipher; -use crate::symm::CipherCtx; +use crate::symm::CipherCtxRef; use std::sync::atomic::{AtomicU8, Ordering}; use std::sync::OnceLock; @@ -148,8 +148,8 @@ fn test_noop_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: &mut CipherCtx, - hmac_ctx: &mut HmacCtx, + evp_ctx: &mut CipherCtxRef, + hmac_ctx: &mut HmacCtxRef, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. @@ -188,8 +188,8 @@ fn test_success_tickey_key_callback( _ssl: &SslRef, key_name: &mut [u8; 16], iv: &mut [u8; ffi::EVP_MAX_IV_LENGTH as usize], - evp_ctx: &mut CipherCtx, - hmac_ctx: &mut HmacCtx, + evp_ctx: &mut CipherCtxRef, + hmac_ctx: &mut HmacCtxRef, encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 831430f5..1a2dc599 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -53,7 +53,7 @@ //! ``` use crate::ffi; -use foreign_types::ForeignType; +use foreign_types::ForeignTypeRef; use libc::{c_int, c_uint}; use openssl_macros::corresponds; use std::cmp; @@ -76,7 +76,7 @@ foreign_type_and_impl_send_sync! { pub struct CipherCtx; } -impl CipherCtx { +impl CipherCtxRef { /// Configures CipherCtx for a fresh encryption operation using `cipher`. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex From 353ea62c17c379dbfcd26bcdeb22c72de382efbb Mon Sep 17 00:00:00 2001 From: Apoorv Kothari Date: Tue, 30 Sep 2025 23:14:09 -0700 Subject: [PATCH 33/39] Convert CipherCtx fns into a safe abstraction. Additional testing. --- boring/src/hmac.rs | 6 +-- boring/src/ssl/test/session_resumption.rs | 58 +++++++++++++---------- boring/src/symm.rs | 26 ++++------ 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/boring/src/hmac.rs b/boring/src/hmac.rs index 9816fb57..7e50e613 100644 --- a/boring/src/hmac.rs +++ b/boring/src/hmac.rs @@ -14,11 +14,7 @@ impl HmacCtxRef { /// Configures HmacCtx to use `md` as the hash function and `key` as the key. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/hmac.h.html#HMAC_Init_ex - /// - /// # Safety - /// - /// The caller must ensure HMAC_CTX has been initalized. - pub unsafe fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { + pub fn init(&mut self, key: &[u8], md: &MessageDigest) -> Result<(), ErrorStack> { ffi::init(); unsafe { diff --git a/boring/src/ssl/test/session_resumption.rs b/boring/src/ssl/test/session_resumption.rs index f7f481db..808abe30 100644 --- a/boring/src/ssl/test/session_resumption.rs +++ b/boring/src/ssl/test/session_resumption.rs @@ -153,6 +153,7 @@ fn test_noop_tickey_key_callback( encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. + const TEST_KEY_NAME: [u8; 16] = [5; 16]; const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -161,24 +162,29 @@ fn test_noop_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { - assert_eq!(key_name, &[0; 16]); - assert_eq!(iv, &[0; 16]); - NOOP_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Ensure key_name and iv are initialized and set test values. + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + key_name.copy_from_slice(&TEST_KEY_NAME); + iv.copy_from_slice(&TEST_CBC_IV); + // Set the encryption context. - unsafe { - evp_ctx - .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) - .unwrap() - }; + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap(); // Set the hmac context. - unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); TicketKeyCallbackResult::Success } else { NOOP_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Check key_name matches. + assert_eq!(key_name, &TEST_KEY_NAME); + TicketKeyCallbackResult::Noop } } @@ -193,6 +199,7 @@ fn test_success_tickey_key_callback( encrypt: bool, ) -> TicketKeyCallbackResult { // These should only be used for testing purposes. + const TEST_KEY_NAME: [u8; 16] = [5; 16]; const TEST_CBC_IV: [u8; ffi::EVP_MAX_IV_LENGTH as usize] = [1; ffi::EVP_MAX_IV_LENGTH as usize]; const TEST_AES_128_CBC_KEY: [u8; 16] = [2; 16]; const TEST_HMAC_KEY: [u8; 32] = [3; 32]; @@ -201,31 +208,34 @@ fn test_success_tickey_key_callback( let cipher = Cipher::aes_128_cbc(); if encrypt { - assert_eq!(key_name, &[0; 16]); - assert_eq!(iv, &[0; 16]); - SUCCESS_ENCRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + // Ensure key_name and iv are initialized and set test values. + assert_eq!(key_name, &[0; 16]); + assert_eq!(iv, &[0; 16]); + key_name.copy_from_slice(&TEST_KEY_NAME); + iv.copy_from_slice(&TEST_CBC_IV); + // Set the encryption context. - unsafe { - evp_ctx - .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) - .unwrap() - }; + evp_ctx + .init_encrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) + .unwrap(); // Set the hmac context. - unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); } else { SUCCESS_DECRYPTION_CALLED_BACK.fetch_add(1, Ordering::SeqCst); + + // Check key_name matches. + assert_eq!(key_name, &TEST_KEY_NAME); + // Set the decryption context. - unsafe { - evp_ctx - .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, &TEST_CBC_IV) - .unwrap() - }; + evp_ctx + .init_decrypt(&cipher, &TEST_AES_128_CBC_KEY, iv) + .unwrap(); // Set the hmac context. - unsafe { hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap() }; + hmac_ctx.init(&TEST_HMAC_KEY, &digest).unwrap(); } TicketKeyCallbackResult::Success diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 1a2dc599..38fab76d 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -80,14 +80,7 @@ impl CipherCtxRef { /// Configures CipherCtx for a fresh encryption operation using `cipher`. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_EncryptInit_ex - /// - /// # Safety - /// - /// The caller must ensure EVP_CIPHER_CTX has been initalized. - /// - /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the - /// chosen Cipher. - pub unsafe fn init_encrypt( + pub fn init_encrypt( &mut self, cipher: &Cipher, key: &[u8], @@ -95,6 +88,10 @@ impl CipherCtxRef { ) -> Result<(), ErrorStack> { ffi::init(); + if key.len() != cipher.key_len() { + return Err(ErrorStack::get()); + } + unsafe { cvt(ffi::EVP_EncryptInit_ex( self.as_ptr(), @@ -111,14 +108,7 @@ impl CipherCtxRef { /// Configures CipherCtx for a fresh decryption operation using `cipher`. /// /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/cipher.h.html#EVP_DecryptInit_ex - /// - /// # Safety - /// - /// The caller must ensure EVP_CIPHER_CTX has been initalized. - /// - /// The caller is responsible for ensuring the length of `key` and `iv` are appropriate for the - /// chosen Cipher. - pub unsafe fn init_decrypt( + pub fn init_decrypt( &mut self, cipher: &Cipher, key: &[u8], @@ -126,6 +116,10 @@ impl CipherCtxRef { ) -> Result<(), ErrorStack> { ffi::init(); + if key.len() != cipher.key_len() { + return Err(ErrorStack::get()); + } + unsafe { cvt(ffi::EVP_DecryptInit_ex( self.as_ptr(), From e3998212eda8d8f51523dfbe24df3638a4a8c9d1 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 11:50:08 +0100 Subject: [PATCH 34/39] Fix string data conversion in ErrorStack::put() --- boring/src/error.rs | 56 +++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index 60d8d4f5..5c1ad40b 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -20,6 +20,7 @@ use openssl_macros::corresponds; use std::borrow::Cow; use std::error; use std::ffi::CStr; +use std::ffi::CString; use std::fmt; use std::io; use std::ptr; @@ -58,7 +59,7 @@ impl ErrorStack { /// Used to report errors from the Rust crate #[cold] pub(crate) fn internal_error(err: impl error::Error) -> Self { - Self(vec![Error::new_internal(err.to_string())]) + Self(vec![Error::new_internal(Data::String(err.to_string()))]) } /// Empties the current thread's error queue. @@ -122,7 +123,14 @@ pub struct Error { code: c_uint, file: *const c_char, line: c_uint, - data: Option>, + data: Data, +} + +#[derive(Clone)] +enum Data { + None, + CString(CString), + String(String), } unsafe impl Sync for Error {} @@ -148,11 +156,9 @@ impl Error { // The memory referenced by data is only valid until that slot is overwritten // in the error stack, so we'll need to copy it off if it's dynamic let data = if flags & ffi::ERR_FLAG_STRING != 0 { - Some(Cow::Owned( - CStr::from_ptr(data.cast()).to_string_lossy().into_owned(), - )) + Data::CString(CStr::from_ptr(data.cast()).to_owned()) } else { - None + Data::None }; Some(Error { code, @@ -176,22 +182,8 @@ impl Error { self.file, self.line, ); - let ptr = match self.data { - Some(Cow::Borrowed(data)) => Some(data.as_ptr() as *mut c_char), - Some(Cow::Owned(ref data)) => { - let ptr = ffi::OPENSSL_malloc((data.len() + 1) as _) as *mut c_char; - if ptr.is_null() { - None - } else { - ptr::copy_nonoverlapping(data.as_ptr(), ptr as *mut u8, data.len()); - *ptr.add(data.len()) = 0; - Some(ptr) - } - } - None => None, - }; - if let Some(ptr) = ptr { - ffi::ERR_add_error_data(1, ptr); + if let Some(cstr) = self.data_cstr() { + ffi::ERR_set_error_data(cstr.as_ptr().cast_mut(), ffi::ERR_FLAG_STRING); } } } @@ -297,15 +289,29 @@ impl Error { /// Returns additional data describing the error. #[must_use] pub fn data(&self) -> Option<&str> { - self.data.as_deref() + match &self.data { + Data::None => None, + Data::CString(cstring) => cstring.to_str().ok(), + Data::String(s) => Some(s), + } } - fn new_internal(msg: String) -> Self { + #[must_use] + fn data_cstr(&self) -> Option> { + let s = match &self.data { + Data::None => return None, + Data::CString(cstr) => return Some(Cow::Borrowed(cstr)), + Data::String(s) => s.as_str(), + }; + CString::new(s).ok().map(Cow::Owned) + } + + fn new_internal(msg: Data) -> Self { Self { code: ffi::ERR_PACK(ffi::ERR_LIB_NONE.0 as _, 0, 0) as _, file: BORING_INTERNAL.as_ptr(), line: 0, - data: Some(msg.into()), + data: msg, } } From 5957ce94cc6e81e6a264e2347b3f57cdab857f50 Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 11:59:59 +0100 Subject: [PATCH 35/39] ErrorStack ctor for custom errors --- boring/src/error.rs | 12 ++++++++++++ boring/src/ssl/callbacks.rs | 2 +- boring/src/symm.rs | 4 ++-- boring/src/util.rs | 4 ++-- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index 5c1ad40b..365643d6 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -38,6 +38,9 @@ pub struct ErrorStack(Vec); impl ErrorStack { /// Pops the contents of the OpenSSL error stack, and returns it. + /// + /// This should be used only immediately after calling Boring FFI functions, + /// otherwise the stack may be empty or a leftover from unrelated calls. #[corresponds(ERR_get_error_line_data)] #[must_use = "Use ErrorStack::clear() to drop the error stack"] pub fn get() -> ErrorStack { @@ -62,6 +65,12 @@ impl ErrorStack { Self(vec![Error::new_internal(Data::String(err.to_string()))]) } + /// Used to report errors from the Rust crate + #[cold] + pub(crate) fn internal_error_str(message: &'static str) -> Self { + Self(vec![Error::new_internal(Data::Static(message))]) + } + /// Empties the current thread's error queue. #[corresponds(ERR_clear_error)] pub(crate) fn clear() { @@ -131,6 +140,7 @@ enum Data { None, CString(CString), String(String), + Static(&'static str), } unsafe impl Sync for Error {} @@ -293,6 +303,7 @@ impl Error { Data::None => None, Data::CString(cstring) => cstring.to_str().ok(), Data::String(s) => Some(s), + Data::Static(s) => Some(s), } } @@ -302,6 +313,7 @@ impl Error { Data::None => return None, Data::CString(cstr) => return Some(Cow::Borrowed(cstr)), Data::String(s) => s.as_str(), + Data::Static(s) => s, }; CString::new(s).ok().map(Cow::Owned) } diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index ed724f79..ea0a73c2 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -767,7 +767,7 @@ impl<'a> CryptoBufferBuilder<'a> { let buffer_capacity = unsafe { ffi::CRYPTO_BUFFER_len(self.buffer) }; if self.cursor.position() != buffer_capacity as u64 { // Make sure all bytes in buffer initialized as required by Boring SSL. - return Err(ErrorStack::get()); + return Err(ErrorStack::internal_error_str("invalid len")); } unsafe { let mut result = ptr::null_mut(); diff --git a/boring/src/symm.rs b/boring/src/symm.rs index 38fab76d..a1346e6e 100644 --- a/boring/src/symm.rs +++ b/boring/src/symm.rs @@ -89,7 +89,7 @@ impl CipherCtxRef { ffi::init(); if key.len() != cipher.key_len() { - return Err(ErrorStack::get()); + return Err(ErrorStack::internal_error_str("invalid key size")); } unsafe { @@ -117,7 +117,7 @@ impl CipherCtxRef { ffi::init(); if key.len() != cipher.key_len() { - return Err(ErrorStack::get()); + return Err(ErrorStack::internal_error_str("invalid key size")); } unsafe { diff --git a/boring/src/util.rs b/boring/src/util.rs index bb6373c1..d34fd898 100644 --- a/boring/src/util.rs +++ b/boring/src/util.rs @@ -55,8 +55,8 @@ where match result { Ok(Ok(len)) => len as c_int, - Ok(Err(_)) => { - // FIXME restore error stack + Ok(Err(err)) => { + err.put(); 0 } Err(err) => { From 75ef5232300a7d005578003c2dfc4d2b38cb7b8a Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 11:12:40 +0100 Subject: [PATCH 36/39] Safer CryptoBufferBuilder::build --- boring/src/ssl/callbacks.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/boring/src/ssl/callbacks.rs b/boring/src/ssl/callbacks.rs index ea0a73c2..f08409b3 100644 --- a/boring/src/ssl/callbacks.rs +++ b/boring/src/ssl/callbacks.rs @@ -16,7 +16,7 @@ use foreign_types::ForeignType; use foreign_types::ForeignTypeRef; use libc::{c_char, c_int, c_uchar, c_uint, c_void}; use std::ffi::CStr; -use std::mem::MaybeUninit; +use std::mem::{self, MaybeUninit}; use std::ptr; use std::slice; use std::str; @@ -769,12 +769,8 @@ impl<'a> CryptoBufferBuilder<'a> { // Make sure all bytes in buffer initialized as required by Boring SSL. return Err(ErrorStack::internal_error_str("invalid len")); } - unsafe { - let mut result = ptr::null_mut(); - ptr::swap(&mut self.buffer, &mut result); - std::mem::forget(self); - Ok(result) - } + // Drop is no-op if the buffer is null + Ok(mem::replace(&mut self.buffer, ptr::null_mut())) } } From 77f612c16c7e8ae008612849e1fbe64f01dcffdb Mon Sep 17 00:00:00 2001 From: Kornel Date: Wed, 1 Oct 2025 10:40:05 +0100 Subject: [PATCH 37/39] Simplify Error::reason() --- boring/src/error.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/boring/src/error.rs b/boring/src/error.rs index 365643d6..1e8f7946 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -103,7 +103,7 @@ impl fmt::Display for ErrorStack { write!( fmt, "[{}]", - err.reason_internal() + err.reason() .or_else(|| err.library()) .unwrap_or("unknown reason") )?; @@ -252,7 +252,10 @@ impl Error { /// Returns the reason for the error. #[must_use] - pub fn reason(&self) -> Option<&'static str> { + pub fn reason(&self) -> Option<&str> { + if self.is_internal() { + return self.data(); + } unsafe { let cstr = ffi::ERR_reason_error_string(self.code); if cstr.is_null() { @@ -330,15 +333,6 @@ impl Error { fn is_internal(&self) -> bool { std::ptr::eq(self.file, BORING_INTERNAL.as_ptr()) } - - // reason() needs 'static - fn reason_internal(&self) -> Option<&str> { - if self.is_internal() { - self.data() - } else { - self.reason() - } - } } impl fmt::Debug for Error { @@ -369,7 +363,7 @@ impl fmt::Display for Error { write!( fmt, "{}\n\nCode: {:08X}\nLoc: {}:{}", - self.reason_internal().unwrap_or("unknown TLS error"), + self.reason().unwrap_or("unknown TLS error"), &self.code, self.file(), self.line() From 5cd912df1db85ec7880d08e577de486452624acb Mon Sep 17 00:00:00 2001 From: Christopher Patton Date: Tue, 30 Sep 2025 11:04:57 -0700 Subject: [PATCH 38/39] Remove "pq-experimental", apply PQ patch by default Users can override the new default behavior in the usual way. The expectation is that the build of BoringSSL they provide the feature set implemented by the patch. --- .github/workflows/ci.yml | 10 +--------- boring-sys/Cargo.toml | 16 ++++++---------- boring-sys/build/config.rs | 7 +------ boring-sys/build/main.rs | 12 ++++-------- boring/Cargo.toml | 17 ++++++----------- hyper-boring/Cargo.toml | 5 +---- tokio-boring/Cargo.toml | 5 +---- 7 files changed, 20 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46e621e7..741dde22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: - name: Run clippy run: cargo clippy --all --all-targets - name: Check docs - run: cargo doc --no-deps -p boring -p boring-sys --features rpk,pq-experimental,underscore-wildcards + run: cargo doc --no-deps -p boring -p boring-sys --features rpk,underscore-wildcards env: DOCS_RS: 1 test: @@ -357,15 +357,7 @@ jobs: shell: bash - run: cargo test --features rpk name: Run `rpk` tests - - run: cargo test --features pq-experimental - name: Run `pq-experimental` tests - run: cargo test --features underscore-wildcards name: Run `underscore-wildcards` tests - - run: cargo test --features pq-experimental,rpk - name: Run `pq-experimental,rpk` tests - - run: cargo test --features pq-experimental,underscore-wildcards - name: Run `pq-experimental,underscore-wildcards` tests - run: cargo test --features rpk,underscore-wildcards name: Run `rpk,underscore-wildcards` tests - - run: cargo test --features pq-experimental,rpk,underscore-wildcards - name: Run `pq-experimental,rpk,underscore-wildcards` tests diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 86ff731c..ce49709d 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -41,7 +41,7 @@ include = [ ] [package.metadata.docs.rs] -features = ["rpk", "pq-experimental", "underscore-wildcards"] +features = ["rpk", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] @@ -56,16 +56,12 @@ fips = [] # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = [] -# Applies a patch (`patches/boring-pq.patch`) to the boringSSL source code that -# enables support for PQ key exchange. This feature is necessary in order to -# compile the bindings for the default branch of boringSSL (`deps/boringssl`). -# Alternatively, a version of boringSSL that implements the same feature set -# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`. -pq-experimental = [] - # Applies a patch (`patches/underscore-wildcards.patch`) to enable -# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as -# those for `pq-experimental` feature apply. +# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. This feature is necessary in +# order to compile the bindings for the default branch of boringSSL +# (`deps/boringssl`). Alternatively, a version of boringSSL that implements the +# same feature set can be provided by setting +# `BORING_BSSL{,_FIPS}_SOURCE_PATH`. underscore-wildcards = [] [build-dependencies] diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs index f586b968..25aaabf4 100644 --- a/boring-sys/build/config.rs +++ b/boring-sys/build/config.rs @@ -16,7 +16,6 @@ pub(crate) struct Config { pub(crate) struct Features { pub(crate) fips: bool, - pub(crate) pq_experimental: bool, pub(crate) rpk: bool, pub(crate) underscore_wildcards: bool, } @@ -89,9 +88,7 @@ impl Config { ); } - let features_with_patches_enabled = self.features.rpk - || self.features.pq_experimental - || self.features.underscore_wildcards; + let features_with_patches_enabled = self.features.rpk || self.features.underscore_wildcards; let patches_required = features_with_patches_enabled && !self.env.assume_patched; @@ -106,13 +103,11 @@ impl Config { impl Features { fn from_env() -> Self { let fips = env::var_os("CARGO_FEATURE_FIPS").is_some(); - let pq_experimental = env::var_os("CARGO_FEATURE_PQ_EXPERIMENTAL").is_some(); let rpk = env::var_os("CARGO_FEATURE_RPK").is_some(); let underscore_wildcards = env::var_os("CARGO_FEATURE_UNDERSCORE_WILDCARDS").is_some(); Self { fips, - pq_experimental, rpk, underscore_wildcards, } diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index c95f1cd4..22e4bccc 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -434,14 +434,12 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { ); return Ok(()); } else if config.env.source_path.is_some() - && (config.features.rpk - || config.features.pq_experimental - || config.features.underscore_wildcards) + && (config.features.rpk || config.features.underscore_wildcards) { panic!( "BORING_BSSL_ASSUME_PATCHED must be set when setting BORING_BSSL_SOURCE_PATH and using any of the following - features: rpk, pq-experimental, underscore-wildcards" + features: rpk, underscore-wildcards" ); } @@ -456,10 +454,8 @@ fn ensure_patches_applied(config: &Config) -> io::Result<()> { run_command(Command::new("git").arg("init").current_dir(src_path))?; } - if config.features.pq_experimental { - println!("cargo:warning=applying experimental post quantum crypto patch to boringssl"); - apply_patch(config, "boring-pq.patch")?; - } + println!("cargo:warning=applying post quantum crypto patch to boringssl"); + apply_patch(config, "boring-pq.patch")?; if config.features.rpk { println!("cargo:warning=applying RPK patch to boringssl"); diff --git a/boring/Cargo.toml b/boring/Cargo.toml index bc9dba22..d76c9294 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -13,7 +13,7 @@ edition = { workspace = true } rust-version = "1.80" [package.metadata.docs.rs] -features = ["rpk", "pq-experimental", "underscore-wildcards"] +features = ["rpk", "underscore-wildcards"] rustdoc-args = ["--cfg", "docsrs"] [features] @@ -32,16 +32,11 @@ legacy-compat-deprecated = [] # `BORING_BSSL{,_FIPS}_SOURCE_PATH` and `BORING_BSSL{,_FIPS}_ASSUME_PATCHED`. rpk = ["boring-sys/rpk"] -# Applies a patch to the boringSSL source code that enables support for PQ key -# exchange. This feature is necessary in order to compile the bindings for the -# 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`. -pq-experimental = ["boring-sys/pq-experimental"] - -# Applies a patch to enable -# `ffi::X509_CHECK_FLAG_UNDERSCORE_WILDCARDS`. Same caveats as -# those for `pq-experimental` feature apply. +# 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 +# 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`. underscore-wildcards = ["boring-sys/underscore-wildcards"] [dependencies] diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 25f360fd..d0f08aab 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -12,16 +12,13 @@ exclude = ["test/*"] rust-version = "1.80" [package.metadata.docs.rs] -features = ["pq-experimental"] +features = [] rustdoc-args = ["--cfg", "docsrs"] [features] # Use a FIPS-validated version of boringssl. fips = ["boring/fips", "tokio-boring/fips"] -# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) -pq-experimental = ["tokio-boring/pq-experimental"] - [dependencies] antidote = { workspace = true } http = { workspace = true } diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index c5735341..15163864 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -12,16 +12,13 @@ An implementation of SSL streams for Tokio backed by BoringSSL """ [package.metadata.docs.rs] -features = ["rpk", "pq-experimental"] +features = ["rpk"] rustdoc-args = ["--cfg", "docsrs"] [features] # Use a FIPS-validated version of boringssl. fips = ["boring/fips", "boring-sys/fips"] -# Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) -pq-experimental = ["boring/pq-experimental"] - # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = ["boring/rpk"] From e23d2d16d4366b9ee687e39d37845d5815ae2856 Mon Sep 17 00:00:00 2001 From: Jaap Aarts Date: Tue, 14 Oct 2025 22:31:38 +0200 Subject: [PATCH 39/39] Update main.rs --- boring-sys/build/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boring-sys/build/main.rs b/boring-sys/build/main.rs index 22e4bccc..41789cee 100644 --- a/boring-sys/build/main.rs +++ b/boring-sys/build/main.rs @@ -556,7 +556,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { // TODO(rmehra): figure out how to do this for windows if env::var_os("CARGO_CFG_UNIX").is_some() { match env::var("CARGO_CFG_TARGET_OS").unwrap().as_ref() { - "macos" | "ios" => Some("c++".into()), + "macos" | "ios" | "freebsd" => Some("c++".into()), _ => Some("stdc++".into()), } } else {