Skip to content

Commit

Permalink
Add fips-3678 feature (#52)
Browse files Browse the repository at this point in the history
* Add rerun-if-env-changed instructions for BORING_* variables

* Use X509_get0_notBefore() and X509_get0_notAfter() instead of X509_getm_notBefore() and X509_getm_notAfter().

According to
https://www.openssl.org/docs/man1.1.0/man3/X509_getm_notBefore.html,
"X509_getm_notBefore() and X509_getm_notAfter() are similar to
X509_get0_notBefore() and X509_get0_notAfter() except they return
non-constant mutable references to the associated date field of the
certificate".

* Only update boringssl submodule if BORING_BSSL_PATH not provided

* Allow BORING_BSSL_LIB_PATH to control link search

* Add fips feature

* Use X509_set_notAfter unconditionally for FIPS compatibility

This is equivalent according to
https://boringssl.googlesource.com/boringssl/+/c947efabcbc38dcf93e8ad0e6a76206cf0ec8072

The version of boringssl that's FIPS-certified doesn't have `X509_set1_notAfter`.
The only difference between that and `X509_set_notAfter` is whether they're const-correct,
which doesn't seem worth having two different code-paths.

* Check out fips commit automatically

* Verify the version of the compiler used for building boringssl

NIST specifies that it needs to be 7.0.1; I originally tried building with clang 10 and it failed.
Theoretically this should check the versions of Go and Ninja too, but they haven't given me trouble in practice.

Example error:
```
   Compiling boring-sys v1.1.1 (/home/jnelson/work/boring/boring-sys)
error: failed to run custom build command for `boring-sys v1.1.1 (/home/jnelson/work/boring/boring-sys)`

Caused by:
  process didn't exit successfully: `/home/jnelson/work/boring/target/debug/build/boring-sys-31b8ce53031cfd83/build-script-build` (exit status: 101)
  --- stdout
  cargo:rerun-if-env-changed=BORING_BSSL_PATH

  --- stderr
  warning: missing clang-7, trying other compilers: Permission denied (os error 13)
  warning: FIPS requires clang version 7.0.1, skipping incompatible version "clang version 10.0.0-4ubuntu1 "
  thread 'main' panicked at 'unsupported clang version "cc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0": FIPS requires clang 7.0.1', boring-sys/build.rs:216:13
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

* Add Github actions workflow testing FIPS

Co-authored-by: Joshua Nelson <[email protected]>
  • Loading branch information
behrat and jyn514 authored Jan 31, 2022
1 parent 5f327ab commit 1507689
Show file tree
Hide file tree
Showing 16 changed files with 209 additions and 45 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,18 @@ jobs:
- if: "!startsWith(matrix.os, 'windows')"
run: cargo test
name: Run tests (not Windows)

test-fips:
name: Test FIPS integration
runs-on: ubuntu-latest
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-7
run: sudo apt-get install -y clang-7
- run: cargo test --features fips
name: Run tests
5 changes: 4 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[submodule "boring-sys/deps/boringssl"]
path = boring-sys/deps/boringssl
url = https://github.com/google/boringssl.git
ignore = dirty
ignore = dirty
[submodule "boring-sys/deps/boringssl-fips"]
path = boring-sys/deps/boringssl-fips
url = https://github.com/google/boringssl.git
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ _Notes_: The crate will look for headers in the `$BORING_BSSL_INCLUDE_PATH/opens

_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

Only BoringCrypto module version ae223d6138807a13006342edfeef32e813246b39, as
certified with [certificate
3678](https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/3678)
is supported by this crate. Support is enabled by this crate's `fips` feature.

`boring-sys` comes with a test that FIPS is enabled/disabled depending on the feature flag. You can run it as follows:
```bash
$ cargo test --features fips fips::is_enabled
```

## Contribution

Unless you explicitly state otherwise, any contribution intentionally
Expand Down
4 changes: 4 additions & 0 deletions boring-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ include = [
[build-dependencies]
bindgen = { version = "0.59", default-features = false, features = ["runtime"] }
cmake = "0.1"

[features]
# Use a FIPS-validated version of boringssl.
fips = []
145 changes: 116 additions & 29 deletions boring-sys/build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use std::path::{Path, PathBuf};
use std::process::Command;

// NOTE: this build script is adopted from quiche (https://github.com/cloudflare/quiche)

// Additional parameters for Android build of BoringSSL.
Expand Down Expand Up @@ -85,6 +88,11 @@ fn get_boringssl_platform_output_path() -> String {
}
}

#[cfg(feature = "fips")]
const BORING_SSL_PATH: &str = "deps/boringssl-fips";
#[cfg(not(feature = "fips"))]
const BORING_SSL_PATH: &str = "deps/boringssl";

/// Returns a new cmake::Config for building BoringSSL.
///
/// It will add platform-specific parameters if needed.
Expand All @@ -93,7 +101,7 @@ fn get_boringssl_cmake_config() -> cmake::Config {
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let pwd = std::env::current_dir().unwrap();

let mut boringssl_cmake = cmake::Config::new("deps/boringssl");
let mut boringssl_cmake = cmake::Config::new(BORING_SSL_PATH);

// Add platform-specific parameters.
match os.as_ref() {
Expand All @@ -105,6 +113,7 @@ fn get_boringssl_cmake_config() -> cmake::Config {
};

// 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")
.expect("Please set ANDROID_NDK_HOME for Android build");
let android_ndk_home = std::path::Path::new(&android_ndk_home);
Expand Down Expand Up @@ -161,7 +170,8 @@ fn get_boringssl_cmake_config() -> cmake::Config {
if arch == "x86" && os != "windows" {
boringssl_cmake.define(
"CMAKE_TOOLCHAIN_FILE",
pwd.join("deps/boringssl/src/util/32-bit-toolchain.cmake")
pwd.join(BORING_SSL_PATH)
.join("src/util/32-bit-toolchain.cmake")
.as_os_str(),
);
}
Expand All @@ -171,42 +181,107 @@ fn get_boringssl_cmake_config() -> cmake::Config {
}
}

fn main() {
use std::env;
use std::path::{Path, PathBuf};
use std::process::Command;

if !Path::new("deps/boringssl/CMakeLists.txt").exists() {
println!("cargo:warning=fetching boringssl git submodule");
// fetch the boringssl submodule
let status = Command::new("git")
.args(&[
"submodule",
"update",
"--init",
"--recursive",
"deps/boringssl",
])
.status();
if !status.map_or(false, |status| status.success()) {
panic!("failed to fetch submodule - consider running `git submodule update --init --recursive deps/boringssl` yourself");
/// Verify that the toolchains match https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3678.pdf
/// See "Installation Instructions" under section 12.1.
// TODO: maybe this should also verify the Go and Ninja versions? But those haven't been an issue in practice ...
fn verify_fips_clang_version() -> (&'static str, &'static str) {
fn version(tool: &str) -> String {
let output = match Command::new(tool).arg("--version").output() {
Ok(o) => o,
Err(e) => {
eprintln!("warning: missing {}, trying other compilers: {}", tool, e);
// NOTE: hard-codes that the loop below checks the version
return String::new();
}
};
assert!(output.status.success());
let output = std::str::from_utf8(&output.stdout).expect("invalid utf8 output");
output.lines().next().expect("empty output").to_string()
}

const REQUIRED_CLANG_VERSION: &str = "7.0.1";
for (cc, cxx) in [
("clang-7", "clang++-7"),
("clang", "clang++"),
("cc", "c++"),
] {
let cc_version = version(cc);
if cc_version.contains(REQUIRED_CLANG_VERSION) {
assert!(
version(cxx).contains(REQUIRED_CLANG_VERSION),
"mismatched versions of cc and c++"
);
return (cc, cxx);
} else if cc == "cc" {
panic!(
"unsupported clang version \"{}\": FIPS requires clang {}",
cc_version, REQUIRED_CLANG_VERSION
);
} else if !cc_version.is_empty() {
eprintln!(
"warning: FIPS requires clang version {}, skipping incompatible version \"{}\"",
REQUIRED_CLANG_VERSION, cc_version
);
}
}
unreachable!()
}

fn main() {
use std::env;

println!("cargo:rerun-if-env-changed=BORING_BSSL_PATH");
let bssl_dir = std::env::var("BORING_BSSL_PATH").unwrap_or_else(|_| {
if !Path::new(BORING_SSL_PATH).join("CMakeLists.txt").exists() {
println!("cargo:warning=fetching boringssl git submodule");
// fetch the boringssl submodule
let status = Command::new("git")
.args(&[
"submodule",
"update",
"--init",
"--recursive",
BORING_SSL_PATH,
])
.status();
if !status.map_or(false, |status| status.success()) {
panic!("failed to fetch submodule - consider running `git submodule update --init --recursive deps/boringssl` yourself");
}
}

let mut cfg = get_boringssl_cmake_config();

if cfg!(feature = "fuzzing") {
cfg.cxxflag("-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE")
.cxxflag("-DBORINGSSL_UNSAFE_FUZZER_MODE");
}
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");
}

cfg.build_target("bssl").build().display().to_string()
});

let build_path = get_boringssl_platform_output_path();
let build_dir = format!("{}/build/{}", bssl_dir, build_path);
println!("cargo:rustc-link-search=native={}", build_dir);
if cfg!(feature = "fips") {
println!(
"cargo:rustc-link-search=native={}/build/crypto/{}",
bssl_dir, build_path
);
println!(
"cargo:rustc-link-search=native={}/build/ssl/{}",
bssl_dir, build_path
);
} else {
println!(
"cargo:rustc-link-search=native={}/build/{}",
bssl_dir, build_path
);
}

println!("cargo:rustc-link-lib=static=crypto");
println!("cargo:rustc-link-lib=static=ssl");
Expand All @@ -216,10 +291,14 @@ fn main() {
println!("cargo:rustc-cdylib-link-arg=-Wl,-undefined,dynamic_lookup");
}

let include_path = PathBuf::from(
std::env::var("BORING_BSSL_INCLUDE_PATH")
.unwrap_or_else(|_| String::from("deps/boringssl/src/include")),
);
println!("cargo:rerun-if-env-changed=BORING_BSSL_INCLUDE_PATH");
let include_path = std::env::var("BORING_BSSL_INCLUDE_PATH").unwrap_or_else(|_| {
if cfg!(feature = "fips") {
format!("{}/include", BORING_SSL_PATH)
} else {
format!("{}/src/include", BORING_SSL_PATH)
}
});

let mut builder = bindgen::Builder::default()
.derive_copy(true)
Expand All @@ -234,12 +313,13 @@ fn main() {
.layout_tests(true)
.prepend_enum_name(true)
.rustfmt_bindings(true)
.clang_args(&["-I", include_path.to_str().unwrap()]);
.clang_args(&["-I", &include_path]);

let headers = [
"aes.h",
"asn1_mac.h",
"asn1t.h",
#[cfg(not(feature = "fips"))]
"blake2.h",
"blowfish.h",
"cast.h",
Expand All @@ -264,11 +344,18 @@ fn main() {
"ripemd.h",
"siphash.h",
"srtp.h",
#[cfg(not(feature = "fips"))]
"trust_token.h",
"x509v3.h",
];
for header in &headers {
builder = builder.header(include_path.join("openssl").join(header).to_str().unwrap());
builder = builder.header(
Path::new(&include_path)
.join("openssl")
.join(header)
.to_str()
.unwrap(),
);
}

let bindings = builder.generate().expect("Unable to generate bindings");
Expand Down
1 change: 1 addition & 0 deletions boring-sys/deps/boringssl-fips
Submodule boringssl-fips added at ae223d
4 changes: 4 additions & 0 deletions boring/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ boring-sys = { version = ">=1.1.0,<3.0.0", path = "../boring-sys" }
[dev-dependencies]
hex = "0.4"
rusty-hook = "^0.11"

[features]
# Use a FIPS-validated version of boringssl.
fips = ["boring-sys/fips"]
3 changes: 3 additions & 0 deletions boring/examples/fips_enabled.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
println!("boring::fips::enabled(): {}", boring::fips::enabled());
}
9 changes: 9 additions & 0 deletions boring/examples/mk_certs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ fn mk_request(privkey: &PKey<Private>) -> Result<X509Req, ErrorStack> {
}

/// Make a certificate and private key signed by the given CA cert and private key
#[cfg_attr(feature = "fips", allow(unreachable_code, unused_variables))]
fn mk_ca_signed_cert(
ca_cert: &X509Ref,
ca_privkey: &PKeyRef<Private>,
Expand All @@ -98,7 +99,15 @@ fn mk_ca_signed_cert(
serial.to_asn1_integer()?
};
cert_builder.set_serial_number(&serial_number)?;

#[cfg(not(feature = "fips"))]
cert_builder.set_subject_name(req.subject_name())?;
#[cfg(feature = "fips")]
{
eprintln!("mk_certs not supported with FIPS module");
std::process::exit(1);
}

cert_builder.set_issuer_name(ca_cert.subject_name())?;
cert_builder.set_pubkey(&privkey)?;
let not_before = Asn1Time::days_from_now(0)?;
Expand Down
8 changes: 8 additions & 0 deletions boring/src/fips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,11 @@ pub fn enable(enabled: bool) -> Result<(), ErrorStack> {
pub fn enabled() -> bool {
unsafe { ffi::FIPS_mode() != 0 }
}

#[test]
fn is_enabled() {
#[cfg(feature = "fips")]
assert!(enabled());
#[cfg(not(feature = "fips"))]
assert!(!enabled());
}
3 changes: 3 additions & 0 deletions boring/src/pkey.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ impl Id {
pub const DH: Id = Id(ffi::EVP_PKEY_DH);
pub const EC: Id = Id(ffi::EVP_PKEY_EC);
pub const ED25519: Id = Id(ffi::EVP_PKEY_ED25519);
#[cfg(not(feature = "fips"))]
pub const ED448: Id = Id(ffi::EVP_PKEY_ED448);
pub const X25519: Id = Id(ffi::EVP_PKEY_X25519);
#[cfg(not(feature = "fips"))]
pub const X448: Id = Id(ffi::EVP_PKEY_X448);

/// Creates a `Id` from an integer representation.
Expand Down Expand Up @@ -289,6 +291,7 @@ impl<T> fmt::Debug for PKey<T> {
Id::DH => "DH",
Id::EC => "EC",
Id::ED25519 => "Ed25519",
#[cfg(not(feature = "fips"))]
Id::ED448 => "Ed448",
_ => "unknown",
};
Expand Down
5 changes: 5 additions & 0 deletions boring/src/ssl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,10 @@ impl ExtensionType {
pub const PADDING: Self = Self(ffi::TLSEXT_TYPE_padding as u16);
pub const EXTENDED_MASTER_SECRET: Self = Self(ffi::TLSEXT_TYPE_extended_master_secret as u16);
pub const TOKEN_BINDING: Self = Self(ffi::TLSEXT_TYPE_token_binding as u16);
#[cfg(not(feature = "fips"))]
pub const QUIC_TRANSPORT_PARAMETERS_LEGACY: Self =
Self(ffi::TLSEXT_TYPE_quic_transport_parameters_legacy as u16);
#[cfg(not(feature = "fips"))]
pub const QUIC_TRANSPORT_PARAMETERS_STANDARD: Self =
Self(ffi::TLSEXT_TYPE_quic_transport_parameters_standard as u16);
pub const CERT_COMPRESSION: Self = Self(ffi::TLSEXT_TYPE_cert_compression as u16);
Expand All @@ -505,8 +507,11 @@ impl ExtensionType {
pub const KEY_SHARE: Self = Self(ffi::TLSEXT_TYPE_key_share as u16);
pub const RENEGOTIATE: Self = Self(ffi::TLSEXT_TYPE_renegotiate as u16);
pub const DELEGATED_CREDENTIAL: Self = Self(ffi::TLSEXT_TYPE_delegated_credential as u16);
#[cfg(not(feature = "fips"))]
pub const APPLICATION_SETTINGS: Self = Self(ffi::TLSEXT_TYPE_application_settings as u16);
#[cfg(not(feature = "fips"))]
pub const ENCRYPTED_CLIENT_HELLO: Self = Self(ffi::TLSEXT_TYPE_encrypted_client_hello as u16);
#[cfg(not(feature = "fips"))]
pub const ECH_IS_INNER: Self = Self(ffi::TLSEXT_TYPE_ech_is_inner as u16);
pub const CERTIFICATE_TIMESTAMP: Self = Self(ffi::TLSEXT_TYPE_certificate_timestamp as u16);
pub const NEXT_PROTO_NEG: Self = Self(ffi::TLSEXT_TYPE_next_proto_neg as u16);
Expand Down
Loading

0 comments on commit 1507689

Please sign in to comment.