diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 5a94948a..20fd892f 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -9,6 +9,7 @@ description = "FFI bindings to BoringSSL" repository = { workspace = true } documentation = "https://docs.rs/boring-sys" links = "boringssl" +build = "build/main.rs" readme = "README.md" categories = ["cryptography", "external-ffi-bindings"] edition = { workspace = true } @@ -44,7 +45,7 @@ include = [ "/deps/boringssl-fips/**/CMakeLists.txt", "/deps/boringssl-fips/**/sources.cmake", "/deps/boringssl-fips/LICENSE", - "/build.rs", + "/build/*", "/src", "/patches", ] @@ -67,15 +68,15 @@ rpk = [] # 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_SOURCE_PATH`. +# can be provided by setting `BORING_BSSL{,_FIPS}_SOURCE_PATH`. pq-experimental = [] -# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, but -# keeps the related Rust API. +# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, +# but keeps the related Rust API. # -# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL_PATH` env variable) or -# with custom BoringSSL sources (via `BORING_BSSL_SOURCE_PATH` env variable) already containing -# required patches. +# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL{,_FIPS}_PATH` env +# variable) or with custom BoringSSL sources (via `BORING_BSSL{,_FIPS}_SOURCE_PATH` env variable) +# already containing required patches. no-patches = [] [build-dependencies] diff --git a/boring-sys/build/config.rs b/boring-sys/build/config.rs new file mode 100644 index 00000000..a927f42a --- /dev/null +++ b/boring-sys/build/config.rs @@ -0,0 +1,143 @@ +use std::env; +use std::ffi::OsString; +use std::path::PathBuf; + +pub(crate) struct Config { + pub(crate) manifest_dir: PathBuf, + pub(crate) out_dir: PathBuf, + pub(crate) host: String, + pub(crate) target: String, + pub(crate) target_arch: String, + pub(crate) target_env: String, + pub(crate) target_os: String, + pub(crate) features: Features, + pub(crate) env: Env, +} + +pub(crate) struct Features { + pub(crate) no_patches: bool, + pub(crate) fips: bool, + pub(crate) fips_link_precompiled: bool, + pub(crate) pq_experimental: bool, + pub(crate) rpk: bool, +} + +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) debug: Option, + pub(crate) opt_level: Option, + pub(crate) android_ndk_home: Option, +} + +impl Config { + pub(crate) fn from_env() -> Self { + let manifest_dir = env::var_os("CARGO_MANIFEST_DIR").unwrap().into(); + let out_dir = env::var_os("OUT_DIR").unwrap().into(); + let host = env::var("HOST").unwrap(); + let target = env::var("TARGET").unwrap(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + + let features = Features::from_env(); + let env = Env::from_env(features.fips || features.fips_link_precompiled); + + let config = Self { + manifest_dir, + out_dir, + host, + target, + target_arch, + target_env, + target_os, + features, + env, + }; + + config.check_feature_compatibility(); + + config + } + + fn check_feature_compatibility(&self) { + if self.features.fips && self.features.rpk { + panic!("`fips` 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(); + + if self.features.no_patches && is_external_native_lib_source { + panic!( + "`no-patches` feature is supposed to be used with `BORING_BSSL{{,_FIPS}}_PATH`\ + or `BORING_BSSL{{,_FIPS}}_SOURCE_PATH` env variables" + ); + } + + let features_with_patches_enabled = self.features.rpk || self.features.pq_experimental; + let patches_required = features_with_patches_enabled && !self.features.no_patches; + let build_from_sources_required = self.features.fips_link_precompiled || patches_required; + + if is_precompiled_native_lib && build_from_sources_required { + panic!("precompiled BoringSSL was provided, so FIPS configuration or optional patches can't be applied"); + } + } +} + +impl Features { + fn from_env() -> Self { + let no_patches = env::var_os("CARGO_FEATURE_NO_PATCHES").is_some(); + let fips = env::var_os("CARGO_FEATURE_FIPS").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(); + + Self { + no_patches, + fips, + fips_link_precompiled, + pq_experimental, + rpk, + } + } +} + +impl Env { + fn from_env(is_fips_like: bool) -> Self { + const NORMAL_PREFIX: &str = "BORING_BSSL"; + const FIPS_PREFIX: &str = "BORING_BSSL_FIPS"; + + let boringssl_var = |name: &str| { + // The passed name is the non-fips version of the environment variable, + // to help look for them in the repository. + assert!(name.starts_with(NORMAL_PREFIX)); + + if is_fips_like { + var(&name.replace(NORMAL_PREFIX, FIPS_PREFIX)) + } else { + var(name) + } + .map(PathBuf::from) + }; + + Self { + path: boringssl_var("BORING_BSSL_PATH"), + include_path: boringssl_var("BORING_BSSL_INCLUDE_PATH"), + source_path: boringssl_var("BORING_BSSL_SOURCE_PATH"), + precompiled_bcm_o: boringssl_var("BORING_BSSL_PRECOMPILED_BCM_O"), + debug: var("DEBUG"), + opt_level: var("OPT_LEVEL"), + android_ndk_home: var("ANDROID_NDK_HOME").map(Into::into), + } + } +} + +fn var(name: &str) -> Option { + println!("cargo:rerun-if-env-changed={name}"); + + env::var_os(name) +} diff --git a/boring-sys/build.rs b/boring-sys/build/main.rs similarity index 59% rename from boring-sys/build.rs rename to boring-sys/build/main.rs index 6f039211..d990a258 100644 --- a/boring-sys/build.rs +++ b/boring-sys/build/main.rs @@ -1,36 +1,15 @@ use fslock::LockFile; -use std::env; use std::ffi::OsString; use std::fs; use std::io; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Output}; -use std::sync::Once; +use std::sync::OnceLock; -// NOTE: this build script is adopted from quiche (https://github.com/cloudflare/quiche) +use crate::config::Config; -// Additional parameters for Android build of BoringSSL. -// -// Android NDK < 18 with GCC. -const CMAKE_PARAMS_ANDROID_NDK_OLD_GCC: &[(&str, &[(&str, &str)])] = &[ - ( - "aarch64", - &[("ANDROID_TOOLCHAIN_NAME", "aarch64-linux-android-4.9")], - ), - ( - "arm", - &[("ANDROID_TOOLCHAIN_NAME", "arm-linux-androideabi-4.9")], - ), - ( - "x86", - &[("ANDROID_TOOLCHAIN_NAME", "x86-linux-android-4.9")], - ), - ( - "x86_64", - &[("ANDROID_TOOLCHAIN_NAME", "x86_64-linux-android-4.9")], - ), -]; +mod config; // Android NDK >= 19. const CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[ @@ -40,15 +19,9 @@ const CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[ ("x86_64", &[("ANDROID_ABI", "x86_64")]), ]; -fn cmake_params_android() -> &'static [(&'static str, &'static str)] { - let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - let cmake_params_android = if cfg!(feature = "ndk-old-gcc") { - CMAKE_PARAMS_ANDROID_NDK_OLD_GCC - } else { - CMAKE_PARAMS_ANDROID_NDK - }; - for (android_arch, params) in cmake_params_android { - if *android_arch == arch { +fn cmake_params_android(config: &Config) -> &'static [(&'static str, &'static str)] { + for (android_arch, params) in CMAKE_PARAMS_ANDROID_NDK { + if *android_arch == config.target_arch { return params; } } @@ -95,71 +68,69 @@ const CMAKE_PARAMS_APPLE: &[(&str, &[(&str, &str)])] = &[ ), ]; -fn cmake_params_apple() -> &'static [(&'static str, &'static str)] { - let target = env::var("TARGET").unwrap(); +fn cmake_params_apple(config: &Config) -> &'static [(&'static str, &'static str)] { for (next_target, params) in CMAKE_PARAMS_APPLE { - if *next_target == target { + if *next_target == config.target { return params; } } &[] } -fn get_apple_sdk_name() -> &'static str { - for (name, value) in cmake_params_apple() { +fn get_apple_sdk_name(config: &Config) -> &'static str { + for (name, value) in cmake_params_apple(config) { if *name == "CMAKE_OSX_SYSROOT" { return value; } } - let target = env::var("TARGET").unwrap(); - panic!("cannot find SDK for {} in CMAKE_PARAMS_APPLE", target); + + panic!( + "cannot find SDK for {} in CMAKE_PARAMS_APPLE", + config.target + ); } /// Returns an absolute path to the BoringSSL source. -fn get_boringssl_source_path() -> String { - #[cfg(feature = "fips")] - const SUBMODULE_DIR: &str = "boringssl-fips"; - #[cfg(not(feature = "fips"))] - const SUBMODULE_DIR: &str = "boringssl"; - - static COPY_SOURCES: Once = Once::new(); - - if let Ok(src_path) = env::var("BORING_BSSL_SOURCE_PATH") { +fn get_boringssl_source_path(config: &Config) -> &PathBuf { + if let Some(src_path) = &config.env.source_path { return src_path; } - let out_dir = env::var("OUT_DIR").unwrap(); - let src_path = Path::new(&out_dir).join(SUBMODULE_DIR); + static SOURCE_PATH: OnceLock = OnceLock::new(); + + SOURCE_PATH.get_or_init(|| { + let submodule_dir = if config.features.fips { + "boringssl-fips" + } else { + "boringssl" + }; - COPY_SOURCES.call_once(|| { - let submodule_path = Path::new(env!("CARGO_MANIFEST_DIR")) - .join("deps") - .join(SUBMODULE_DIR); + let src_path = config.out_dir.join(submodule_dir); + + let submodule_path = config.manifest_dir.join("deps").join(submodule_dir); if !submodule_path.join("CMakeLists.txt").exists() { println!("cargo:warning=fetching boringssl git submodule"); - run_command(Command::new("git").args([ - "submodule", - "update", - "--init", - "--recursive", - &submodule_path.display().to_string(), - ])) + run_command( + Command::new("git") + .args(["submodule", "update", "--init", "--recursive"]) + .arg(&submodule_path), + ) .unwrap(); } let _ = fs::remove_dir_all(&src_path); - fs_extra::dir::copy(submodule_path, &out_dir, &Default::default()).unwrap(); + fs_extra::dir::copy(submodule_path, &config.out_dir, &Default::default()).unwrap(); // NOTE: .git can be both file and dir, depening on whether it was copied from a submodule // or created by the patches code. let src_git_path = src_path.join(".git"); let _ = fs::remove_file(&src_git_path); let _ = fs::remove_dir_all(&src_git_path); - }); - src_path.display().to_string() + src_path + }) } /// Returns the platform-specific output path for lib. @@ -167,30 +138,38 @@ fn get_boringssl_source_path() -> String { /// 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 -fn get_boringssl_platform_output_path() -> String { - if cfg!(target_env = "msvc") { +fn get_boringssl_platform_output_path(config: &Config) -> String { + if config.target_env == "msvc" { // Code under this branch should match the logic in cmake-rs - let debug_env_var = env::var("DEBUG").expect("DEBUG variable not defined in env"); - - let deb_info = match &debug_env_var[..] { - "false" => false, - "true" => true, - unknown => panic!("Unknown DEBUG={} env var.", unknown), + let debug_env_var = config + .env + .debug + .as_ref() + .expect("DEBUG variable not defined in env"); + + let deb_info = match debug_env_var.to_str() { + Some("false") => false, + Some("true") => true, + _ => panic!("Unknown DEBUG={:?} env var.", debug_env_var), }; - let opt_env_var = env::var("OPT_LEVEL").expect("OPT_LEVEL variable not defined in env"); + let opt_env_var = config + .env + .opt_level + .as_ref() + .expect("OPT_LEVEL variable not defined in env"); - let subdir = match &opt_env_var[..] { - "0" => "Debug", - "1" | "2" | "3" => { + let subdir = match opt_env_var.to_str() { + Some("0") => "Debug", + Some("1" | "2" | "3") => { if deb_info { "RelWithDebInfo" } else { "Release" } } - "s" | "z" => "MinSizeRel", - unknown => panic!("Unknown OPT_LEVEL={} env var.", unknown), + Some("s" | "z") => "MinSizeRel", + _ => panic!("Unknown OPT_LEVEL={:?} env var.", opt_env_var), }; subdir.to_string() @@ -202,26 +181,22 @@ fn get_boringssl_platform_output_path() -> String { /// Returns a new cmake::Config for building BoringSSL. /// /// It will add platform-specific parameters if needed. -fn get_boringssl_cmake_config() -> cmake::Config { - let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); - let host = env::var("HOST").unwrap(); - let target = env::var("TARGET").unwrap(); - let pwd = std::env::current_dir().unwrap(); - let src_path = get_boringssl_source_path(); - - let mut boringssl_cmake = cmake::Config::new(&src_path); - if host != target { +fn get_boringssl_cmake_config(config: &Config) -> cmake::Config { + let src_path = get_boringssl_source_path(config); + let mut boringssl_cmake = cmake::Config::new(src_path); + + if config.host != config.target { // Add platform-specific parameters for cross-compilation. - match os.as_ref() { + match &*config.target_os { "android" => { // We need ANDROID_NDK_HOME to be set properly. - println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME"); - let android_ndk_home = env::var("ANDROID_NDK_HOME") + let android_ndk_home = config + .env + .android_ndk_home + .as_ref() .expect("Please set ANDROID_NDK_HOME for Android build"); - let android_ndk_home = std::path::Path::new(&android_ndk_home); - for (name, value) in cmake_params_android() { - eprintln!("android arch={} add {}={}", arch, name, value); + for (name, value) in cmake_params_android(config) { + eprintln!("android arch={} add {}={}", config.target_arch, name, value); boringssl_cmake.define(name, value); } let toolchain_file = android_ndk_home.join("build/cmake/android.toolchain.cmake"); @@ -235,15 +210,15 @@ fn get_boringssl_cmake_config() -> cmake::Config { } "macos" => { - for (name, value) in cmake_params_apple() { - eprintln!("macos arch={} add {}={}", arch, name, value); + for (name, value) in cmake_params_apple(config) { + eprintln!("macos arch={} add {}={}", config.target_arch, name, value); boringssl_cmake.define(name, value); } } "ios" => { - for (name, value) in cmake_params_apple() { - eprintln!("ios arch={} add {}={}", arch, name, value); + for (name, value) in cmake_params_apple(config) { + eprintln!("ios arch={} add {}={}", config.target_arch, name, value); boringssl_cmake.define(name, value); } @@ -251,7 +226,7 @@ fn get_boringssl_cmake_config() -> cmake::Config { let bitcode_cflag = "-fembed-bitcode"; // Hack for Xcode 10.1. - let target_cflag = if arch == "x86_64" { + let target_cflag = if config.target_arch == "x86_64" { "-target x86_64-apple-ios-simulator" } else { "" @@ -263,18 +238,22 @@ fn get_boringssl_cmake_config() -> cmake::Config { } "windows" => { - if host.contains("windows") { + if config.host.contains("windows") { // BoringSSL's CMakeLists.txt isn't set up for cross-compiling using Visual Studio. // Disable assembly support so that it at least builds. boringssl_cmake.define("OPENSSL_NO_ASM", "YES"); } } - "linux" => match arch.as_str() { + "linux" => match &*config.target_arch { "x86" => { boringssl_cmake.define( "CMAKE_TOOLCHAIN_FILE", - pwd.join(&src_path) + // `src_path` can be a path relative to the manifest dir, but + // cmake hates that. + config + .manifest_dir + .join(src_path) .join("src/util/32-bit-toolchain.cmake") .as_os_str(), ); @@ -282,19 +261,25 @@ fn get_boringssl_cmake_config() -> cmake::Config { "aarch64" => { boringssl_cmake.define( "CMAKE_TOOLCHAIN_FILE", - pwd.join("cmake/aarch64-linux.cmake").as_os_str(), + config + .manifest_dir + .join("cmake/aarch64-linux.cmake") + .as_os_str(), ); } "arm" => { boringssl_cmake.define( "CMAKE_TOOLCHAIN_FILE", - pwd.join("cmake/armv7-linux.cmake").as_os_str(), + config + .manifest_dir + .join("cmake/armv7-linux.cmake") + .as_os_str(), ); } _ => { eprintln!( "warning: no toolchain file configured by boring-sys for {}", - target + config.target ); } }, @@ -381,18 +366,16 @@ fn pick_best_android_ndk_toolchain(toolchains_dir: &Path) -> std::io::Result Vec { - let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); - +fn get_extra_clang_args_for_bindgen(config: &Config) -> Vec { let mut params = Vec::new(); // Add platform-specific parameters. #[allow(clippy::single_match)] - match os.as_ref() { + match &*config.target_os { "ios" | "macos" => { // When cross-compiling for Apple targets, tell bindgen to use SDK sysroot, // and *don't* use system headers of the host macOS. - let sdk = get_apple_sdk_name(); + let sdk = get_apple_sdk_name(config); let output = std::process::Command::new("xcrun") .args(["--show-sdk-path", "--sdk", sdk]) .output() @@ -414,10 +397,14 @@ fn get_extra_clang_args_for_bindgen() -> Vec { params.push(sysroot); } "android" => { - let android_ndk_home = env::var("ANDROID_NDK_HOME") + let mut android_sysroot = config + .env + .android_ndk_home + .clone() .expect("Please set ANDROID_NDK_HOME for Android build"); - let mut android_sysroot = std::path::PathBuf::from(android_ndk_home); + android_sysroot.extend(["toolchains", "llvm", "prebuilt"]); + let toolchain = match pick_best_android_ndk_toolchain(&android_sysroot) { Ok(toolchain) => toolchain, Err(e) => { @@ -432,8 +419,6 @@ fn get_extra_clang_args_for_bindgen() -> Vec { android_sysroot.push(toolchain); android_sysroot.push("sysroot"); params.push("--sysroot".to_string()); - // If ANDROID_NDK_HOME weren't a valid UTF-8 string, - // we'd already know from env::var. params.push(android_sysroot.into_os_string().into_string().unwrap()); } _ => {} @@ -442,48 +427,43 @@ fn get_extra_clang_args_for_bindgen() -> Vec { params } -fn ensure_patches_applied() -> io::Result<()> { - let out_dir = env::var("OUT_DIR").unwrap(); - let mut lock_file = LockFile::open(&PathBuf::from(&out_dir).join(".patch_lock"))?; - let src_path = get_boringssl_source_path(); - let has_git = Path::new(&src_path).join(".git").exists(); +fn ensure_patches_applied(config: &Config) -> io::Result<()> { + 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(); lock_file.lock()?; // NOTE: init git in the copied files, so we can apply patches if !has_git { - run_command(Command::new("git").args(["init"]).current_dir(&src_path))?; + run_command(Command::new("git").arg("init").current_dir(src_path))?; } - if cfg!(feature = "pq-experimental") { + if config.features.pq_experimental { println!("cargo:warning=applying experimental post quantum crypto patch to boringssl"); - apply_patch("boring-pq.patch")?; + apply_patch(config, "boring-pq.patch")?; } - if cfg!(feature = "rpk") { + if config.features.rpk { println!("cargo:warning=applying RPK patch to boringssl"); - apply_patch("rpk.patch")?; + apply_patch(config, "rpk.patch")?; } Ok(()) } -fn apply_patch(patch_name: &str) -> io::Result<()> { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let src_path = get_boringssl_source_path(); - let cmd_path = manifest_dir +fn apply_patch(config: &Config, patch_name: &str) -> io::Result<()> { + let src_path = get_boringssl_source_path(config); + let cmd_path = config + .manifest_dir .join("patches") .join(patch_name) .canonicalize()?; run_command( Command::new("git") - .args([ - "apply", - "-v", - "--whitespace=fix", - &cmd_path.display().to_string(), - ]) + .args(["apply", "-v", "--whitespace=fix"]) + .arg(cmd_path) .current_dir(src_path), )?; @@ -508,58 +488,66 @@ fn run_command(command: &mut Command) -> io::Result { Ok(out) } -fn build_boring_from_sources() -> String { - if cfg!(feature = "no-patches") { - println!( - "cargo:warning=skipping git patches application, provided\ - native BoringSSL is expected to have the patches included" - ); - } else { - ensure_patches_applied().unwrap(); +fn built_boring_source_path(config: &Config) -> &PathBuf { + if let Some(path) = &config.env.path { + return path; } - let mut cfg = get_boringssl_cmake_config(); + static BUILD_SOURCE_PATH: OnceLock = OnceLock::new(); - if cfg!(feature = "fuzzing") { - cfg.cxxflag("-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE") - .cxxflag("-DBORINGSSL_UNSAFE_FUZZER_MODE"); - } + BUILD_SOURCE_PATH.get_or_init(|| { + if config.features.no_patches { + println!( + "cargo:warning=skipping git patches application, provided\ + native BoringSSL is expected to have the patches included" + ); + } else { + ensure_patches_applied(config).unwrap(); + } - if cfg!(feature = "fips") { - let (clang, clangxx) = verify_fips_clang_version(); - cfg.define("CMAKE_C_COMPILER", clang); - cfg.define("CMAKE_CXX_COMPILER", clangxx); - cfg.define("CMAKE_ASM_COMPILER", clang); - cfg.define("FIPS", "1"); - } + let mut cfg = get_boringssl_cmake_config(config); - if cfg!(feature = "fips-link-precompiled") { - cfg.define("FIPS", "1"); - } + 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) + .define("FIPS", "1"); + } - cfg.build_target("ssl").build(); - cfg.build_target("crypto").build().display().to_string() + 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(bssl_dir: &str) { +fn link_in_precompiled_bcm_o(config: &Config) { println!("cargo:warning=linking in precompiled `bcm.o` module"); - let bcm_o_src_path = env::var("BORING_SSL_PRECOMPILED_BCM_O") - .expect("`fips-link-precompiled` requires `BORING_SSL_PRECOMPILED_BCM_O` env variable to be specified"); + 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 = PathBuf::from(bssl_dir) + let libcrypto_path = bssl_dir .join("build/crypto/libcrypto.a") .canonicalize() - .unwrap() - .display() - .to_string(); + .unwrap(); - let bcm_o_dst_path = PathBuf::from(bssl_dir).join("build/bcm-fips.o"); + 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").args(["t", &libcrypto_path, "bcm.o"])).unwrap(); + 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(), @@ -572,90 +560,62 @@ fn link_in_precompiled_bcm_o(bssl_dir: &str) { // (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", - &libcrypto_path, - bcm_o_dst_path.display().to_string().as_str(), - ])) + run_command( + Command::new("ar") + .args(["rb", "bcm.o"]) + .args([&libcrypto_path, &bcm_o_dst_path]), + ) .unwrap(); } -fn check_feature_compatibility() { - #[cfg(all(feature = "fips", feature = "rpk"))] - compile_error!("`fips` and `rpk` features are mutually exclusive"); - - let no_patches_enabled = cfg!(feature = "no-patches"); - let is_external_native_lib_source = - env::var("BORING_BSSL_PATH").is_err() && env::var("BORING_BSSL_SOURCE_PATH").is_err(); - - if no_patches_enabled && is_external_native_lib_source { - panic!( - "`no-patches` feature is supposed to be used with `BORING_BSSL_PATH`\ - or `BORING_BSSL_SOURCE_PATH` env variables" - ) - } - - let features_with_patches_enabled = cfg!(any(feature = "rpk", feature = "pq-experimental")); - let patches_required = features_with_patches_enabled && !no_patches_enabled; - let build_from_sources_required = cfg!(feature = "fips-link-precompiled") || patches_required; - let is_precompiled_native_lib = env::var("BORING_BSSL_PATH").is_ok(); - - if is_precompiled_native_lib && build_from_sources_required { - panic!("precompiled BoringSSL was provided, so FIPS configuration or optional patches can't be applied"); - } -} - fn main() { - println!("cargo:rerun-if-env-changed=BORING_BSSL_PATH"); - println!("cargo:rerun-if-env-changed=BORING_BSSL_INCLUDE_PATH"); - println!("cargo:rerun-if-env-changed=BORING_BSSL_SOURCE_PATH"); - println!("cargo:rerun-if-env-changed=BORING_SSL_PRECOMPILED_BCM_O"); - println!("cargo:rerun-if-env-changed=BORINGSSL_BUILD_DIR"); + let config = Config::from_env(); + let bssl_dir = built_boring_source_path(&config); + let build_path = get_boringssl_platform_output_path(&config); - check_feature_compatibility(); - - let bssl_dir = env::var("BORING_BSSL_PATH").unwrap_or_else(|_| build_boring_from_sources()); - let build_path = get_boringssl_platform_output_path(); - - if cfg!(any(feature = "fips", feature = "fips-link-precompiled")) { + if config.features.fips || config.features.fips_link_precompiled { println!( "cargo:rustc-link-search=native={}/build/crypto/{}", - bssl_dir, build_path + bssl_dir.display(), + build_path ); println!( "cargo:rustc-link-search=native={}/build/ssl/{}", - bssl_dir, build_path + bssl_dir.display(), + build_path ); println!( "cargo:rustc-link-search=native={}/lib/{}", - bssl_dir, build_path + bssl_dir.display(), + build_path ); } else { println!( "cargo:rustc-link-search=native={}/build/{}", - bssl_dir, build_path + bssl_dir.display(), + build_path ); } - if cfg!(feature = "fips-link-precompiled") { - link_in_precompiled_bcm_o(&bssl_dir); + if config.features.fips_link_precompiled { + link_in_precompiled_bcm_o(&config); } println!("cargo:rustc-link-lib=static=crypto"); println!("cargo:rustc-link-lib=static=ssl"); - let include_path = env::var("BORING_BSSL_INCLUDE_PATH").unwrap_or_else(|_| { - if let Ok(bssl_path) = env::var("BORING_BSSL_PATH") { - return format!("{}/include", bssl_path); + let include_path = config.env.include_path.clone().unwrap_or_else(|| { + if let Some(bssl_path) = &config.env.path { + return bssl_path.join("include"); } - let src_path = get_boringssl_source_path(); + let src_path = get_boringssl_source_path(&config); + let candidate = src_path.join("include"); - if Path::new(&src_path).join("include").exists() { - format!("{}/include", &src_path) + if candidate.exists() { + candidate } else { - format!("{}/src/include", &src_path) + src_path.join("src").join("include") } }); @@ -674,11 +634,11 @@ fn main() { .size_t_is_usize(true) .layout_tests(true) .prepend_enum_name(true) - .clang_args(get_extra_clang_args_for_bindgen()) - .clang_args(&["-I", &include_path]); + .clang_args(get_extra_clang_args_for_bindgen(&config)) + .clang_arg("-I") + .clang_arg(include_path.display().to_string()); - let target = env::var("TARGET").unwrap(); - match target.as_ref() { + match &*config.target { // bindgen produces alignment tests that cause undefined behavior [1] // when applied to explicitly unaligned types like OSUnalignedU64. // @@ -726,18 +686,11 @@ fn main() { "x509v3.h", ]; for header in &headers { - builder = builder.header( - Path::new(&include_path) - .join("openssl") - .join(header) - .to_str() - .unwrap(), - ); + builder = builder.header(include_path.join("openssl").join(header).to_str().unwrap()); } let bindings = builder.generate().expect("Unable to generate bindings"); - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings - .write_to_file(out_path.join("bindings.rs")) + .write_to_file(config.out_dir.join("bindings.rs")) .expect("Couldn't write bindings!"); } diff --git a/boring/Cargo.toml b/boring/Cargo.toml index 8d983fe8..717960b9 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -31,15 +31,15 @@ rpk = ["boring-sys/rpk"] # 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_SOURCE_PATH`. +# `BORING_BSSL{,_FIPS}_SOURCE_PATH`. pq-experimental = ["boring-sys/pq-experimental"] -# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, but -# keeps the related Rust API. +# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, +# but keeps the related Rust API. # -# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL_PATH` env variable) or -# with custom BoringSSL sources (via `BORING_BSSL_SOURCE_PATH` env variable) already containing -# required patches. +# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL{,_FIPS}_PATH` env +# variable) or with custom BoringSSL sources (via `BORING_BSSL{,_FIPS}_SOURCE_PATH` env variable) +# already containing required patches. no-patches = ["boring-sys/no-patches"] # Controlling key exchange preferences at compile time diff --git a/boring/src/lib.rs b/boring/src/lib.rs index b9d68fe7..27c1ebcd 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -18,18 +18,26 @@ //! //! # Compilation and linking options //! +//! ## Environment variables +//! +//! This crate uses various environment variables to tweak how boring is built. The variables +//! are all prefixed by `BORING_BSSL_` for non-FIPS builds, and by `BORING_BSSL_FIPS_` for FIPS builds. +//! //! ## Support for pre-built binaries or custom source //! //! While this crate can build BoringSSL on its own, you may want to provide pre-built binaries instead. -//! To do so, specify the environment variable `BORING_BSSL_PATH` with the path to the binaries. +//! To do so, specify the environment variable `BORING_BSSL{,_FIPS}_PATH` with the path to the binaries. //! -//! You can also provide specific headers by setting `BORING_BSSL_INCLUDE_PATH`. +//! You can also provide specific headers by setting `BORING_BSSL{,_FIPS}_INCLUDE_PATH`. //! -//! _Notes_: The crate will look for headers in the `$BORING_BSSL_INCLUDE_PATH/openssl/` folder, make sure to place your headers there. +//! _Notes_: The crate will look for headers in the`$BORING_BSSL{,_FIPS}_INCLUDE_PATH/openssl/` +//! folder, make sure to place your headers there. //! -//! In alternative a different path for the BoringSSL source code directory can be specified by setting `BORING_BSSL_SOURCE_PATH` which will automatically be compiled during the build process. +//! In alternative a different path for the BoringSSL source code directory can be specified by setting +//! `BORING_BSSL{,_FIPS}_SOURCE_PATH` which will automatically be compiled during the build process. //! -//! _Warning_: When providing a different version of BoringSSL make sure to use a compatible one, the crate relies on the presence of certain functions. +//! _Warning_: When providing a different version of BoringSSL make sure to use a compatible one, the +//! crate relies on the presence of certain functions. //! //! ## Building with a FIPS-validated module //! @@ -44,11 +52,15 @@ //! ``` //! //! ## Linking current BoringSSL version with precompiled FIPS-validated module (`bcm.o`) +//! //! It's possible to link latest supported version of BoringSSL with FIPS-validated crypto module //! (`bcm.o`). To enable this compilation option one should enable `fips-link-precompiled` -//! compilation feature and provide a `BORING_SSL_PRECOMPILED_BCM_O` env variable with a path to the +//! compilation feature and provide a `BORING_BSSL_FIPS_PRECOMPILED_BCM_O` env variable with a path to the //! precompiled FIPS-validated `bcm.o` module. //! +//! Note that `BORING_BSSL_PRECOMPILED_BCM_O` is never used, as linking BoringSSL with precompiled non-FIPS +//! module is not supported. +//! //! # Optional patches //! //! ## Raw Public Key diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index 321f3a60..07d0a182 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -31,12 +31,12 @@ rpk = ["tokio-boring/rpk"] # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["tokio-boring/pq-experimental"] -# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, but -# keeps the related Rust API. +# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, +# but keeps the related Rust API. # -# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL_PATH` env variable) or -# with custom BoringSSL sources (via `BORING_BSSL_SOURCE_PATH` env variable) already containing -# required patches. +# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL{,_FIPS}_PATH` env +# variable) or with custom BoringSSL sources (via `BORING_BSSL{,_FIPS}_SOURCE_PATH` env variable) +# already containing required patches. no-patches = ["tokio-boring/no-patches"] diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index 509ab6f7..31a967b3 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -28,12 +28,12 @@ rpk = ["boring/rpk"] # Enables experimental post-quantum crypto (https://blog.cloudflare.com/post-quantum-for-all/) pq-experimental = ["boring/pq-experimental"] -# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, but -# keeps the related Rust API. +# Disables git patching of the BoringSSL sources for features like `rpk` and `pq-experimental`, +# but keeps the related Rust API. # -# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL_PATH` env variable) or -# with custom BoringSSL sources (via `BORING_BSSL_SOURCE_PATH` env variable) already containing -# required patches. +# Supposed to be used with either pre-compiled BoringSSL (via `BORING_BSSL{,_FIPS}_PATH` env +# variable) or with custom BoringSSL sources (via `BORING_BSSL{,_FIPS}_SOURCE_PATH` env variable) +# already containing required patches. no-patches = ["boring/no-patches"] [dependencies]