diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ebec5a822..1dbfaf205 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -214,3 +214,34 @@ jobs: name: Run `pq-experimental` tests - run: cargo test --features pq-experimental,rpk name: Run `pq-experimental,rpk` tests + + test-fips-link-precompiled: + name: Test precompiled bcm.o linking + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + - 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: Add clang++-12 link + working-directory: ${{ runner.temp }}/llvm/bin + run: ln -s clang clang++-12 + - name: Build FIPS boring + run: (cd boring-sys && cargo build --features fips) + - name: Copy bcm.o to tmp + run: find . -name "bcm.o" -exec cp "{}" ${{ runner.temp }} \; + - name: Clean the build dir + run: cargo clean + - name: Run tests + run: cargo test --features fips-link-precompiled + env: + BORING_SSL_PRECOMPILED_BCM_O: ${{ runner.temp }}/bcm.o + + diff --git a/boring-sys/Cargo.toml b/boring-sys/Cargo.toml index 254d6f718..6c31d0020 100644 --- a/boring-sys/Cargo.toml +++ b/boring-sys/Cargo.toml @@ -36,6 +36,9 @@ rustdoc-args = ["--cfg", "docsrs"] # Use a FIPS-validated version of boringssl. fips = [] +# Link with precompiled FIPS-validated `bcm.o` module. +fips-link-precompiled = ["fips"] + # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = [] diff --git a/boring-sys/build.rs b/boring-sys/build.rs index c98fb64d2..873cfe1e4 100644 --- a/boring-sys/build.rs +++ b/boring-sys/build.rs @@ -1,9 +1,10 @@ use fslock::LockFile; use std::env; +use std::fs; use std::io; use std::io::Write; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Command, Output}; // NOTE: this build script is adopted from quiche (https://github.com/cloudflare/quiche) @@ -38,7 +39,7 @@ const CMAKE_PARAMS_ANDROID_NDK: &[(&str, &[(&str, &str)])] = &[ ]; fn cmake_params_android() -> &'static [(&'static str, &'static str)] { - let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + 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 { @@ -77,7 +78,7 @@ const CMAKE_PARAMS_IOS: &[(&str, &[(&str, &str)])] = &[ ]; fn cmake_params_ios() -> &'static [(&'static str, &'static str)] { - let target = std::env::var("TARGET").unwrap(); + let target = env::var("TARGET").unwrap(); for (ios_target, params) in CMAKE_PARAMS_IOS { if *ios_target == target { return params; @@ -92,18 +93,18 @@ fn get_ios_sdk_name() -> &'static str { return value; } } - let target = std::env::var("TARGET").unwrap(); + let target = env::var("TARGET").unwrap(); panic!("cannot find iOS SDK for {} in CMAKE_PARAMS_IOS", target); } /// Returns an absolute path to the BoringSSL source. fn get_boringssl_source_path() -> String { - #[cfg(feature = "fips")] + #[cfg(all(feature = "fips", not(feature = "fips-link-precompiled")))] const BORING_SSL_SOURCE_PATH: &str = "deps/boringssl-fips"; - #[cfg(not(feature = "fips"))] + #[cfg(any(not(feature = "fips"), feature = "fips-link-precompiled"))] const BORING_SSL_SOURCE_PATH: &str = "deps/boringssl"; - std::env::var("BORING_BSSL_SOURCE_PATH") + env::var("BORING_BSSL_SOURCE_PATH") .unwrap_or(env!("CARGO_MANIFEST_DIR").to_owned() + "/" + BORING_SSL_SOURCE_PATH) } @@ -115,7 +116,7 @@ fn get_boringssl_source_path() -> String { fn get_boringssl_platform_output_path() -> String { if cfg!(target_env = "msvc") { // Code under this branch should match the logic in cmake-rs - let debug_env_var = std::env::var("DEBUG").expect("DEBUG variable not defined in env"); + let debug_env_var = env::var("DEBUG").expect("DEBUG variable not defined in env"); let deb_info = match &debug_env_var[..] { "false" => false, @@ -123,8 +124,7 @@ fn get_boringssl_platform_output_path() -> String { unknown => panic!("Unknown DEBUG={} env var.", unknown), }; - let opt_env_var = - std::env::var("OPT_LEVEL").expect("OPT_LEVEL variable not defined in env"); + let opt_env_var = env::var("OPT_LEVEL").expect("OPT_LEVEL variable not defined in env"); let subdir = match &opt_env_var[..] { "0" => "Debug", @@ -149,10 +149,10 @@ fn get_boringssl_platform_output_path() -> String { /// /// It will add platform-specific parameters if needed. fn get_boringssl_cmake_config() -> cmake::Config { - let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); - let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); - let host = std::env::var("HOST").unwrap(); - let target = std::env::var("TARGET").unwrap(); + 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(); @@ -163,7 +163,7 @@ fn get_boringssl_cmake_config() -> cmake::Config { "android" => { // We need ANDROID_NDK_HOME to be set properly. println!("cargo:rerun-if-env-changed=ANDROID_NDK_HOME"); - let android_ndk_home = std::env::var("ANDROID_NDK_HOME") + let android_ndk_home = env::var("ANDROID_NDK_HOME") .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() { @@ -288,7 +288,7 @@ fn verify_fips_clang_version() -> (&'static str, &'static str) { } fn get_extra_clang_args_for_bindgen() -> Vec { - let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); + let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let mut params = Vec::new(); @@ -320,13 +320,13 @@ fn get_extra_clang_args_for_bindgen() -> Vec { params.push(sysroot); } "android" => { - let android_ndk_home = std::env::var("ANDROID_NDK_HOME") + let android_ndk_home = env::var("ANDROID_NDK_HOME") .expect("Please set ANDROID_NDK_HOME for Android build"); let mut android_sysroot = std::path::PathBuf::from(android_ndk_home); 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 std::env::var. + // we'd already know from env::var. params.push(android_sysroot.into_os_string().into_string().unwrap()); } _ => {} @@ -367,11 +367,11 @@ fn ensure_patches_applied() -> io::Result<()> { Ok(()) } -fn run_command(command: &mut Command) -> io::Result<()> { - let exit_status = command.spawn()?.wait()?; +fn run_command(command: &mut Command) -> io::Result { + let out = command.output()?; - if !exit_status.success() { - let err = match exit_status.code() { + if !out.status.success() { + let err = match out.status.code() { Some(code) => format!("{:?} exited with status: {}", command, code), None => format!("{:?} was terminated by signal", command), }; @@ -379,7 +379,7 @@ fn run_command(command: &mut Command) -> io::Result<()> { return Err(io::Error::new(io::ErrorKind::Other, err)); } - Ok(()) + Ok(out) } fn run_apply_patch_script(script_path: impl AsRef) -> io::Result<()> { @@ -417,7 +417,11 @@ fn build_boring_from_sources() -> String { cfg.cxxflag("-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE") .cxxflag("-DBORINGSSL_UNSAFE_FUZZER_MODE"); } - if cfg!(feature = "fips") { + + if cfg!(all( + feature = "fips", + not(feature = "fips-link-precompiled") + )) { let (clang, clangxx) = verify_fips_clang_version(); cfg.define("CMAKE_C_COMPILER", clang); cfg.define("CMAKE_CXX_COMPILER", clangxx); @@ -425,22 +429,70 @@ fn build_boring_from_sources() -> String { cfg.define("FIPS", "1"); } + if cfg!(feature = "fips-link-precompiled") { + cfg.define("FIPS", "1"); + } + cfg.build_target("ssl").build(); cfg.build_target("crypto").build().display().to_string() } +fn link_in_precompiled_bcm_o(bssl_dir: &str) -> io::Result<()> { + 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 libcrypto_path = PathBuf::from(bssl_dir) + .join("build/crypto/libcrypto.a") + .canonicalize()? + .display() + .to_string(); + + let bcm_o_dst_path = PathBuf::from(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"]))?; + + assert_eq!( + String::from_utf8(out.stdout).unwrap(), + "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", + &libcrypto_path, + bcm_o_dst_path.display().to_string().as_str(), + ]))?; + + Ok(()) +} + fn main() { println!("cargo:rerun-if-env-changed=BORING_BSSL_PATH"); #[cfg(all(feature = "fips", feature = "rpk"))] compile_error!("`fips` and `rpk` features are mutually exclusive"); - let bssl_dir = std::env::var("BORING_BSSL_PATH"); + let bssl_dir = env::var("BORING_BSSL_PATH"); if bssl_dir.is_ok() && cfg!(any(feature = "rpk", feature = "pq-experimental")) { panic!("precompiled BoringSSL was provided, optional patches can't be applied to it"); } + if bssl_dir.is_ok() && cfg!(feature = "fips") { + panic!("precompiled BoringSSL was provided, so FIPS configuration can't be applied"); + } + let bssl_dir = bssl_dir.unwrap_or_else(|_| build_boring_from_sources()); let build_path = get_boringssl_platform_output_path(); @@ -461,11 +513,15 @@ fn main() { ); } + if cfg!(feature = "fips-link-precompiled") { + link_in_precompiled_bcm_o(&bssl_dir).unwrap(); + } + println!("cargo:rustc-link-lib=static=crypto"); println!("cargo:rustc-link-lib=static=ssl"); println!("cargo:rerun-if-env-changed=BORING_BSSL_INCLUDE_PATH"); - let include_path = std::env::var("BORING_BSSL_INCLUDE_PATH").unwrap_or_else(|_| { + let include_path = env::var("BORING_BSSL_INCLUDE_PATH").unwrap_or_else(|_| { let src_path = get_boringssl_source_path(); if cfg!(feature = "fips") { format!("{}/include", &src_path) @@ -492,7 +548,7 @@ fn main() { .clang_args(get_extra_clang_args_for_bindgen()) .clang_args(&["-I", &include_path]); - let target = std::env::var("TARGET").unwrap(); + let target = env::var("TARGET").unwrap(); match target.as_ref() { // bindgen produces alignment tests that cause undefined behavior [1] // when applied to explicitly unaligned types like OSUnalignedU64. diff --git a/boring/Cargo.toml b/boring/Cargo.toml index 4244dcb0d..a6f546b47 100644 --- a/boring/Cargo.toml +++ b/boring/Cargo.toml @@ -19,6 +19,9 @@ rustdoc-args = ["--cfg", "docsrs"] # Use a FIPS-validated version of boringssl. fips = ["boring-sys/fips"] +# Link with precompiled FIPS-validated `bcm.o` module. +fips-link-precompiled = ["fips"] + # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = ["boring-sys/rpk"] diff --git a/boring/src/lib.rs b/boring/src/lib.rs index ceefe616e..348a346ae 100644 --- a/boring/src/lib.rs +++ b/boring/src/lib.rs @@ -43,6 +43,12 @@ //! $ cargo test --features fips fips::is_enabled //! ``` //! +//! ## 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 +//! precompiled FIPS-validated `bcm.o` module. +//! //! # Optional patches //! //! ## Raw Public Key diff --git a/hyper-boring/Cargo.toml b/hyper-boring/Cargo.toml index f0f8356e5..e086388b8 100644 --- a/hyper-boring/Cargo.toml +++ b/hyper-boring/Cargo.toml @@ -22,6 +22,9 @@ runtime = ["hyper/runtime"] # Use a FIPS-validated version of boringssl. fips = ["tokio-boring/fips"] +# Link with precompiled FIPS-validated `bcm.o` module. +fips-link-precompiled = ["fips"] + # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = ["tokio-boring/rpk"] diff --git a/tokio-boring/Cargo.toml b/tokio-boring/Cargo.toml index f827b6f39..0da4a0586 100644 --- a/tokio-boring/Cargo.toml +++ b/tokio-boring/Cargo.toml @@ -19,6 +19,9 @@ rustdoc-args = ["--cfg", "docsrs"] # Use a FIPS-validated version of boringssl. fips = ["boring/fips"] +# Link with precompiled FIPS-validated `bcm.o` module. +fips-link-precompiled = ["fips"] + # Enables Raw public key API (https://datatracker.ietf.org/doc/html/rfc7250) rpk = ["boring/rpk"]