diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9d5a8cc2..2cb40bb1c 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,10 +31,11 @@ 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)" + run: | + echo "version=$(rustc --version)" >> $GITHUB_OUTPUT - name: Cache cargo index uses: actions/cache@v4 with: @@ -58,6 +59,10 @@ jobs: key: clippy-target-${{ runner.os }}-${{ steps.rust-version.outputs.version }}-${{ hashFiles('Cargo.lock') }} - 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 + env: + DOCS_RS: 1 test: name: Test runs-on: ${{ matrix.os }} @@ -141,8 +146,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 @@ -151,8 +156,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/Cargo.toml b/Cargo.toml index e45e98497..eb99271d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ resolver = "2" [workspace.package] -version = "4.18.0" +version = "4.20.0" repository = "https://github.com/cloudflare/boring" edition = "2021" @@ -19,16 +19,17 @@ tag-prefix = "" publish = false [workspace.dependencies] -boring-sys = { version = "4.18.0", path = "./boring-sys" } -boring = { version = "4.18.0", path = "./boring" } -tokio-boring = { version = "4.18.0", path = "./tokio-boring" } +boring-sys = { version = "4.19.0", path = "./boring-sys" } +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,5 +49,3 @@ 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/RELEASE_NOTES b/RELEASE_NOTES index fe369a6f8..450684229 100644 --- a/RELEASE_NOTES +++ b/RELEASE_NOTES @@ -1,3 +1,24 @@ +4.20.0 +- 2025-08-26 Support TARGET_CC and CC_{target} +- 2025-08-26 Fix swapped host/target args +- 2025-06-13 CStr UTF-8 improvements +- 2025-09-26 Skip Rust version detection for bindgen +- 2025-09-26 Upgrade deps +- 2025-06-13 Ensure that ERR_LIB type can be named +- 2025-06-13 Add more reliable library_reason() +- 2025-09-30 pq: fix MSVC C4146 warning +- 2025-10-14 Freebsd build +- 2025-10-01 Fix string data conversion in ErrorStack::put() + +4.19.0 +- 2025-09-03 Add binding for X509_check_ip_asc +- 2025-06-13 Use ERR_clear_error +- 2025-06-13 Error descriptions and docs +- 2025-06-13 Boring doesn't use function codes +- 2025-09-03 Fix patched docs.rs builds +- 2025-09-03 Test docs.rs docs +- 2025-09-03 Fix doc links + 4.18.0 - 2025-05-29 Add set_verify_param - 2025-05-28 Add support for X509_STORE_CTX_get0_untrusted diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 5cd8e214d..166d62f55 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/config.rs b/boring-sys/build/config.rs index c40f611dc..5d93eda4a 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, } @@ -51,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, @@ -142,22 +145,19 @@ 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"; + 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 cb0e9fe36..06a30d1fe 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 { @@ -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); } @@ -331,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) { @@ -468,6 +477,24 @@ fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec { } fn ensure_patches_applied(config: &Config) -> io::Result<()> { + if config.env.assume_patched || config.env.path.is_some() { + println!( + "cargo:warning=skipping git patches application, provided\ + native BoringSSL is expected to have the patches included" + ); + return Ok(()); + } else if config.env.source_path.is_some() + && (config.features.rpk + || config.features.pq_experimental + || 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" + ); + } + let mut lock_file = LockFile::open(&config.out_dir.join(".patch_lock"))?; let src_path = get_boringssl_source_path(config); let has_git = src_path.join(".git").exists(); @@ -552,25 +579,6 @@ fn built_boring_source_path(config: &Config) -> &PathBuf { static BUILD_SOURCE_PATH: OnceLock = OnceLock::new(); BUILD_SOURCE_PATH.get_or_init(|| { - if config.env.assume_patched { - println!( - "cargo:warning=skipping git patches application, provided\ - native BoringSSL is expected to have the patches included" - ); - } else if config.env.source_path.is_some() - && (config.features.rpk - || config.features.pq_experimental - || 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" - ); - } else { - ensure_patches_applied(config).unwrap(); - } - let mut cfg = get_boringssl_cmake_config(config); let num_jobs = std::env::var("NUM_JOBS").ok().or_else(|| { @@ -651,7 +659,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 { @@ -661,6 +669,7 @@ fn get_cpp_runtime_lib(config: &Config) -> Option { fn main() { let config = Config::from_env(); + ensure_patches_applied(&config).unwrap(); if !config.env.docs_rs { emit_link_directives(&config); } @@ -732,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 @@ -753,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)) @@ -805,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(), + ); } diff --git a/boring-sys/patches/boring-pq.patch b/boring-sys/patches/boring-pq.patch index 384880044..b3afacf66 100644 --- a/boring-sys/patches/boring-pq.patch +++ b/boring-sys/patches/boring-pq.patch @@ -940,7 +940,7 @@ index 776c085f9..ccb5b3d9b 100644 + for(i=0;i> 63; ++ return (0-(uint64_t)r) >> 63; +} + +/************************************************* diff --git a/boring/examples/mk_certs.rs b/boring/examples/mk_certs.rs index a130dee6c..1fc4993d8 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/asn1.rs b/boring/src/asn1.rs index 0275cccb0..e00214941 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. diff --git a/boring/src/bio.rs b/boring/src/bio.rs index 6566ebb89..71120606f 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/derive.rs b/boring/src/derive.rs index 701d48a3a..1952118d4 100644 --- a/boring/src/derive.rs +++ b/boring/src/derive.rs @@ -51,7 +51,6 @@ impl<'a> Deriver<'a> { /// /// It can be used to size the buffer passed to [`Deriver::derive`]. #[corresponds(EVP_PKEY_derive)] - /// [`EVP_PKEY_derive`]: https://www.openssl.org/docs/man1.0.2/crypto/EVP_PKEY_derive_init.html pub fn len(&mut self) -> Result { unsafe { let mut len = 0; diff --git a/boring/src/dh.rs b/boring/src/dh.rs index c8449d86f..8e0c68fba 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/error.rs b/boring/src/error.rs index c81978c55..5c1ad40bb 100644 --- a/boring/src/error.rs +++ b/boring/src/error.rs @@ -15,10 +15,12 @@ //! Err(e) => println!("Parsing Error: {:?}", e), //! } //! ``` -use libc::{c_char, c_uint}; +use libc::{c_char, c_int, c_uint}; +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; @@ -26,6 +28,8 @@ use std::str; use crate::ffi; +pub use crate::ffi::ErrLib; + /// Collection of [`Error`]s from OpenSSL. /// /// [`Error`]: struct.Error.html @@ -34,7 +38,8 @@ pub struct ErrorStack(Vec); impl ErrorStack { /// Pops the contents of the OpenSSL error stack, and returns it. - #[allow(clippy::must_use_candidate)] + #[corresponds(ERR_get_error_line_data)] + #[must_use = "Use ErrorStack::clear() to drop the error stack"] pub fn get() -> ErrorStack { let mut vec = vec![]; while let Some(err) = Error::get() { @@ -44,6 +49,7 @@ impl ErrorStack { } /// Pushes the errors back onto the OpenSSL error stack. + #[corresponds(ERR_put_error)] pub fn put(&self) { for error in self.errors() { error.put(); @@ -53,7 +59,15 @@ 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. + #[corresponds(ERR_clear_error)] + pub(crate) fn clear() { + unsafe { + ffi::ERR_clear_error(); + } } } @@ -80,7 +94,9 @@ impl fmt::Display for ErrorStack { write!( fmt, "[{}]", - err.reason_internal().unwrap_or("unknown reason") + err.reason_internal() + .or_else(|| err.library()) + .unwrap_or("unknown reason") )?; } Ok(()) @@ -101,13 +117,20 @@ impl From for fmt::Error { } } -/// An error reported from OpenSSL. +/// A detailed error reported as part of an [`ErrorStack`]. #[derive(Clone)] 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 {} @@ -117,7 +140,8 @@ static BORING_INTERNAL: &CStr = c"boring-rust"; impl Error { /// Pops the first error off the OpenSSL error stack. - #[allow(clippy::must_use_candidate)] + #[must_use = "Use ErrorStack::clear() to drop the error stack"] + #[corresponds(ERR_get_error_line_data)] pub fn get() -> Option { unsafe { ffi::init(); @@ -132,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 { - let bytes = CStr::from_ptr(data as *const _).to_bytes(); - let data = String::from_utf8_lossy(bytes).into_owned(); - Some(data.into()) + Data::CString(CStr::from_ptr(data.cast()).to_owned()) } else { - None + Data::None }; Some(Error { code, @@ -150,6 +172,7 @@ impl Error { } /// Pushes the error back onto the OpenSSL error stack. + #[corresponds(ERR_put_error)] pub fn put(&self) { unsafe { ffi::ERR_put_error( @@ -159,28 +182,29 @@ 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); } } } - /// Returns the raw OpenSSL error code for this 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::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 } @@ -196,32 +220,24 @@ 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") } } - /// Returns the raw OpenSSL error constant for the library reporting the - /// error. + /// Returns the raw OpenSSL error constant for the library reporting the error (`ERR_LIB_{name}`). + /// + /// Error [reason codes](Error::library_reason) are not globally unique, but scoped to each library. #[must_use] - pub fn library_code(&self) -> libc::c_int { + pub fn library_code(&self) -> c_int { ffi::ERR_GET_LIB(self.code) } - /// Returns the name of the function reporting the error. - #[must_use] + /// Returns `None`. Boring doesn't use function codes. pub fn function(&self) -> Option<&'static str> { - if self.is_internal() { - return None; - } - unsafe { - let cstr = ffi::ERR_func_error_string(self.code); - if cstr.is_null() { - return None; - } - let bytes = CStr::from_ptr(cstr as *const _).to_bytes(); - str::from_utf8(bytes).ok() - } + None } /// Returns the reason for the error. @@ -232,14 +248,19 @@ 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() } } - /// Returns the raw OpenSSL error constant for the reason for the 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. #[must_use] - pub fn reason_code(&self) -> libc::c_int { + pub fn reason_code(&self) -> c_int { ffi::ERR_GET_REASON(self.code) } @@ -250,12 +271,15 @@ 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() } } /// Returns the line in the source file which encountered the error. + /// + /// 0 if unknown #[allow(clippy::unnecessary_cast)] #[must_use] pub fn line(&self) -> u32 { @@ -265,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, } } @@ -294,20 +332,19 @@ impl Error { impl fmt::Debug for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let mut builder = fmt.debug_struct("Error"); - builder.field("code", &self.code()); - if let Some(library) = self.library() { - builder.field("library", &library); - } - builder.field("library_code", &self.library_code()); - if let Some(function) = self.function() { - builder.field("function", &function); - } - if let Some(reason) = self.reason() { - builder.field("reason", &reason); + builder.field("code", &self.code); + if !self.is_internal() { + if let Some(library) = self.library() { + builder.field("library", &library); + } + builder.field("library_code", &self.library_code()); + if let Some(reason) = self.reason() { + builder.field("reason", &reason); + } + builder.field("reason_code", &self.reason_code()); + builder.field("file", &self.file()); + builder.field("line", &self.line()); } - builder.field("reason_code", &self.reason_code()); - builder.field("file", &self.file()); - builder.field("line", &self.line()); if let Some(data) = self.data() { builder.field("data", &data); } @@ -321,7 +358,7 @@ impl fmt::Display for Error { fmt, "{}\n\nCode: {:08X}\nLoc: {}:{}", self.reason_internal().unwrap_or("unknown TLS error"), - self.code(), + &self.code, self.file(), self.line() ) diff --git a/boring/src/nid.rs b/boring/src/nid.rs index 43fc19f45..347b30f74 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/rsa.rs b/boring/src/rsa.rs index 5a0ae24d4..ff47e71ba 100644 --- a/boring/src/rsa.rs +++ b/boring/src/rsa.rs @@ -413,7 +413,6 @@ impl Rsa { /// `n` is the modulus common to both public and private key. /// `e` is the public exponent. #[corresponds(RSA_new)] - /// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html pub fn from_public_components(n: BigNum, e: BigNum) -> Result, ErrorStack> { unsafe { let rsa = cvt_p(ffi::RSA_new())?; @@ -472,7 +471,6 @@ impl RsaPrivateKeyBuilder { /// `n` is the modulus common to both public and private key. /// `e` is the public exponent and `d` is the private exponent. #[corresponds(RSA_new)] - /// [`RSA_set0_key`]: https://www.openssl.org/docs/man1.1.0/crypto/RSA_set0_key.html pub fn new(n: BigNum, e: BigNum, d: BigNum) -> Result { unsafe { let rsa = cvt_p(ffi::RSA_new())?; diff --git a/boring/src/sign.rs b/boring/src/sign.rs index 3a4a7e8fc..b87e60107 100644 --- a/boring/src/sign.rs +++ b/boring/src/sign.rs @@ -478,7 +478,7 @@ impl<'a> Verifier<'a> { match r { 1 => Ok(true), 0 => { - ErrorStack::get(); // discard error stack + ErrorStack::clear(); // discard error stack Ok(false) } _ => Err(ErrorStack::get()), @@ -500,7 +500,7 @@ impl<'a> Verifier<'a> { match r { 1 => Ok(true), 0 => { - ErrorStack::get(); + ErrorStack::clear(); Ok(false) } _ => Err(ErrorStack::get()), diff --git a/boring/src/ssl/async_callbacks.rs b/boring/src/ssl/async_callbacks.rs index 93226b485..4ab18d11a 100644 --- a/boring/src/ssl/async_callbacks.rs +++ b/boring/src/ssl/async_callbacks.rs @@ -11,7 +11,7 @@ use std::pin::Pin; use std::sync::LazyLock; use std::task::{ready, Context, Poll, Waker}; -/// The type of futures to pass to [`SslContextBuilderExt::set_async_select_certificate_callback`]. +/// The type of futures to pass to [`SslContextBuilder::set_async_select_certificate_callback`]. pub type BoxSelectCertFuture = ExDataFuture>; /// The type of callbacks returned by [`BoxSelectCertFuture`] methods. @@ -25,19 +25,19 @@ pub type BoxPrivateKeyMethodFuture = pub type BoxPrivateKeyMethodFinish = Box Result>; -/// The type of futures to pass to [`SslContextBuilderExt::set_async_get_session_callback`]. +/// The type of futures to pass to [`SslContextBuilder::set_async_get_session_callback`]. pub type BoxGetSessionFuture = ExDataFuture>; /// The type of callbacks returned by [`BoxSelectCertFuture`] methods. pub type BoxGetSessionFinish = Box Option>; -/// The type of futures to pass to [`SslContextBuilderExt::set_async_custom_verify_callback`]. +/// The type of futures to pass to [`SslContextBuilder::set_async_custom_verify_callback`]. pub type BoxCustomVerifyFuture = ExDataFuture>; /// The type of callbacks returned by [`BoxCustomVerifyFuture`] methods. pub type BoxCustomVerifyFinish = Box Result<(), SslAlert>>; -/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilderExt`] methods. +/// Convenience alias for futures stored in [`Ssl`] ex data by [`SslContextBuilder`] methods. /// /// Public for documentation purposes. pub type ExDataFuture = Pin + Send>>; @@ -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. @@ -123,7 +123,7 @@ impl SslContextBuilder { /// /// # Safety /// - /// The returned [`SslSession`] must not be associated with a different [`SslContext`]. + /// The returned [`SslSession`] must not be associated with a different [`SslContextBuilder`]. pub unsafe fn set_async_get_session_callback(&mut self, callback: F) where F: Fn(&mut SslRef, &[u8]) -> Option + Send + Sync + 'static, @@ -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 8ad4ba55a..f618e591d 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; @@ -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/error.rs b/boring/src/ssl/error.rs index f62e5f329..5acad8200 100644 --- a/boring/src/ssl/error.rs +++ b/boring/src/ssl/error.rs @@ -1,16 +1,20 @@ use crate::ffi; use crate::x509::X509VerifyError; use libc::c_int; +use openssl_macros::corresponds; use std::error; use std::error::Error as StdError; +use std::ffi::CStr; use std::fmt; use std::io; use crate::error::ErrorStack; use crate::ssl::MidHandshakeSslStream; -/// An error code returned from SSL functions. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +/// `SSL_ERROR_*` error code returned from SSL functions. +/// +/// This is different than [packed error codes](crate::error::Error). +#[derive(Copy, Clone, PartialEq, Eq)] pub struct ErrorCode(c_int); impl ErrorCode { @@ -50,16 +54,52 @@ impl ErrorCode { /// An error occurred in the SSL library. pub const SSL: ErrorCode = ErrorCode(ffi::SSL_ERROR_SSL); + /// Wrap an `SSL_ERROR_*` error code. + /// + /// This is different than [packed error codes](crate::error::Error). #[must_use] + #[inline] + #[cfg_attr(debug_assertions, track_caller)] pub fn from_raw(raw: c_int) -> ErrorCode { - ErrorCode(raw) + let code = ErrorCode(raw); + debug_assert!( + raw < 64 || code.description().is_some(), + "{raw} is not an SSL_ERROR_* code" + ); + code } + /// An `SSL_ERROR_*` error code. + /// + /// This is different than [packed error codes](crate::error::Error). #[allow(clippy::trivially_copy_pass_by_ref)] #[must_use] pub fn as_raw(&self) -> c_int { self.0 } + + #[corresponds(SSL_error_description)] + pub fn description(self) -> Option<&'static str> { + unsafe { + let msg = ffi::SSL_error_description(self.0); + if msg.is_null() { + return None; + } + CStr::from_ptr(msg).to_str().ok() + } + } +} + +impl fmt::Display for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ({})", self.description().unwrap_or("error"), self.0) + } +} + +impl fmt::Debug for ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } } #[derive(Debug)] @@ -68,7 +108,7 @@ pub(crate) enum InnerError { Ssl(ErrorStack), } -/// An SSL error. +/// A general SSL error, based on [`SSL_ERROR_*` error codes](ErrorCode). #[derive(Debug)] pub struct Error { pub(crate) code: ErrorCode, @@ -76,6 +116,7 @@ pub struct Error { } impl Error { + /// An `SSL_ERROR_*` error code. #[must_use] pub fn code(&self) -> ErrorCode { self.code @@ -96,6 +137,7 @@ impl Error { } } + /// Stack of [library-specific errors](crate::error::Error), if available. #[must_use] pub fn ssl_error(&self) -> Option<&ErrorStack> { match self.cause { @@ -131,26 +173,27 @@ impl From for Error { impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self.code { - ErrorCode::ZERO_RETURN => fmt.write_str("the SSL session has been shut down"), + let msg = match self.code { + ErrorCode::ZERO_RETURN => "the SSL session has been shut down", ErrorCode::WANT_READ => match self.io_error() { - Some(_) => fmt.write_str("a nonblocking read call would have blocked"), - None => fmt.write_str("the operation should be retried"), + Some(_) => "a nonblocking read call would have blocked", + None => "the operation should be retried", }, ErrorCode::WANT_WRITE => match self.io_error() { - Some(_) => fmt.write_str("a nonblocking write call would have blocked"), - None => fmt.write_str("the operation should be retried"), + Some(_) => "a nonblocking write call would have blocked", + None => "the operation should be retried", }, ErrorCode::SYSCALL => match self.io_error() { - Some(err) => write!(fmt, "{err}"), - None => fmt.write_str("unexpected EOF"), + Some(err) => return err.fmt(fmt), + None => "unexpected EOF", }, ErrorCode::SSL => match self.ssl_error() { - Some(e) => write!(fmt, "{e}"), - None => fmt.write_str("unknown BoringSSL error"), + Some(err) => return err.fmt(fmt), + None => "unknown BoringSSL error", }, - ErrorCode(code) => write!(fmt, "unknown error code {code}"), - } + ErrorCode(code) => return code.fmt(fmt), + }; + fmt.write_str(msg) } } diff --git a/boring/src/ssl/mod.rs b/boring/src/ssl/mod.rs index 90934d06e..9cd40405c 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. @@ -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. @@ -4028,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) + }) } } } @@ -4455,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 05c401f16..4527f0d7d 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 f429d33be..e0e0b3d0c 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}; @@ -169,11 +168,10 @@ impl X509StoreContextRef { /// * `cert_chain` - The certificates chain. /// * `with_context` - The closure that is called with the initialized context. /// - /// This corresponds to [`X509_STORE_CTX_init`] before calling `with_context` and to - /// [`X509_STORE_CTX_cleanup`] after calling `with_context`. + /// Calls [`X509_STORE_CTX_cleanup`] after calling `with_context`. /// - /// [`X509_STORE_CTX_init`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_init.html /// [`X509_STORE_CTX_cleanup`]: https://www.openssl.org/docs/man1.0.2/crypto/X509_STORE_CTX_cleanup.html + #[corresponds(X509_STORE_CTX_init)] pub fn init( &mut self, trust: &store::X509StoreRef, @@ -789,6 +787,13 @@ impl X509Ref { } } + #[corresponds(X509_check_ip_asc)] + pub fn check_ip_asc(&self, address: &str) -> Result { + let c_str = CString::new(address).map_err(ErrorStack::internal_error)?; + + unsafe { cvt_n(ffi::X509_check_ip_asc(self.as_ptr(), c_str.as_ptr(), 0)).map(|n| n == 1) } + } + to_pem! { /// Serializes the certificate into a PEM-encoded X509 structure. /// @@ -859,7 +864,7 @@ impl X509 { if ffi::ERR_GET_LIB(err) == ffi::ERR_LIB_PEM.0.try_into().unwrap() && ffi::ERR_GET_REASON(err) == ffi::PEM_R_NO_START_LINE { - ffi::ERR_clear_error(); + ErrorStack::clear(); break; } @@ -902,7 +907,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() @@ -1371,10 +1376,7 @@ pub struct X509ReqBuilder(X509Req); impl X509ReqBuilder { /// Returns a builder for a certificate request. - /// - /// This corresponds to [`X509_REQ_new`]. - /// - ///[`X509_REQ_new`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_new.html + #[corresponds(X509_REQ_new)] pub fn new() -> Result { unsafe { ffi::init(); @@ -1383,10 +1385,7 @@ impl X509ReqBuilder { } /// Set the numerical value of the version field. - /// - /// This corresponds to [`X509_REQ_set_version`]. - /// - ///[`X509_REQ_set_version`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_set_version.html + #[corresponds(X509_REQ_set_version)] pub fn set_version(&mut self, version: i32) -> Result<(), ErrorStack> { unsafe { cvt(ffi::X509_REQ_set_version(self.0.as_ptr(), version.into())).map(|_| ()) } } @@ -1544,10 +1543,7 @@ impl X509ReqRef { } /// Returns the public key of the certificate request. - /// - /// This corresponds to [`X509_REQ_get_pubkey"] - /// - /// [`X509_REQ_get_pubkey`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_get_pubkey.html + #[corresponds(X509_REQ_get_pubkey)] pub fn public_key(&self) -> Result, ErrorStack> { unsafe { let key = cvt_p(ffi::X509_REQ_get_pubkey(self.as_ptr()))?; @@ -1558,10 +1554,7 @@ impl X509ReqRef { /// Check if the certificate request is signed using the given public key. /// /// Returns `true` if verification succeeds. - /// - /// This corresponds to [`X509_REQ_verify"]. - /// - /// [`X509_REQ_verify`]: https://www.openssl.org/docs/man1.1.0/crypto/X509_REQ_verify.html + #[corresponds(X509_REQ_verify)] pub fn verify(&self, key: &PKeyRef) -> Result where T: HasPublic, @@ -1570,8 +1563,7 @@ impl X509ReqRef { } /// Returns the extensions of the certificate request. - /// - /// This corresponds to [`X509_REQ_get_extensions"] + #[corresponds(X509_REQ_get_extensions)] pub fn extensions(&self) -> Result, ErrorStack> { unsafe { let extensions = cvt_p(ffi::X509_REQ_get_extensions(self.as_ptr()))?; @@ -1626,6 +1618,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] @@ -1634,7 +1628,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() } } } @@ -1786,14 +1780,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() } } @@ -1823,10 +1815,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()) } } } @@ -1902,8 +1891,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; diff --git a/boring/src/x509/tests/mod.rs b/boring/src/x509/tests/mod.rs index 06b53bb0f..ba96141b5 100644 --- a/boring/src/x509/tests/mod.rs +++ b/boring/src/x509/tests/mod.rs @@ -836,3 +836,16 @@ fn test_load_subject_der() { ]; X509Name::from_der(SUBJECT_DER).unwrap(); } + +#[test] +fn test_check_ip_asc() { + // Covers 127.0.0.1 and 0:0:0:0:0:0:0:1 + let cert = include_bytes!("../../../test/alt_name_cert.pem"); + let cert = X509::from_pem(cert).unwrap(); + + assert!(cert.check_ip_asc("127.0.0.1").unwrap()); + assert!(!cert.check_ip_asc("127.0.0.2").unwrap()); + + assert!(cert.check_ip_asc("0:0:0:0:0:0:0:1").unwrap()); + assert!(!cert.check_ip_asc("0:0:0:0:0:0:0:2").unwrap()); +} diff --git a/tokio-boring/src/bridge.rs b/tokio-boring/src/bridge.rs index 62ed7729f..5fdd1c30c 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