Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fips-3678 feature #52

Merged
merged 16 commits into from
Jan 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = "0.59"
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");
behrat marked this conversation as resolved.
Show resolved Hide resolved
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", 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 @@ -286,6 +288,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