Skip to content

opt-in rustls-ffi FIPS support, Linux CI coverage #478

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

Merged
merged 2 commits into from
Nov 25, 2024
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
18 changes: 18 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,24 @@ jobs:
- name: Integration tests
run: make PROFILE=debug CERT_COMPRESSION=true integration

# TODO(@cpu): MacOS and Windows FIPS test coverage
fips:
name: FIPS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install nightly rust toolchain
uses: dtolnay/rust-toolchain@nightly
- name: Unit tests
run: make FIPS=true test
- name: Integration tests
run: make FIPS=true integration

test-windows-cmake-debug:
name: Windows CMake, Debug configuration
runs-on: windows-latest
Expand Down
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ endif ()

set(CERT_COMPRESSION "false" CACHE STRING "Whether to enable brotli and zlib certificate compression support")

set(FIPS "false" CACHE STRING "Whether to enable aws-lc-rs and FIPS support")

set(CARGO_FEATURES --no-default-features)
if (CRYPTO_PROVIDER STREQUAL "aws-lc-rs")
list(APPEND CARGO_FEATURES --features=aws-lc-rs)
Expand All @@ -21,6 +23,11 @@ if (CERT_COMPRESSION STREQUAL "true")
list(APPEND CARGO_FEATURES --features=cert_compression)
endif ()

# See https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html
if (FIPS STREQUAL "true")
list(APPEND CARGO_FEATURES --features=fips)
endif ()

add_subdirectory(tests)

include(ExternalProject)
Expand Down
16 changes: 16 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ capi = []
ring = ["rustls/ring", "webpki/ring"]
aws-lc-rs = ["rustls/aws-lc-rs", "webpki/aws_lc_rs"]
cert_compression = ["rustls/brotli", "rustls/zlib"]
fips = ["aws-lc-rs", "rustls/fips"]

[dependencies]
# Keep in sync with RUSTLS_CRATE_VERSION in build.rs
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ CFLAGS := -Werror -Wall -Wextra -Wpedantic -g -I src/
PROFILE := release
CRYPTO_PROVIDER := aws-lc-rs
COMPRESSION := false
FIPS := false
DESTDIR=/usr/local

ifeq ($(PROFILE), debug)
Expand Down Expand Up @@ -41,6 +42,11 @@ ifeq ($(COMPRESSION), true)
LDFLAGS += -lm
endif

# See https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html
ifeq ($(FIPS), true)
CARGOFLAGS += --features fips
endif

default: target/$(PROFILE)/librustls_ffi.a

all: default test integration connect-test
Expand Down
6 changes: 6 additions & 0 deletions Makefile.pkg-config
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CFLAGS := -Werror -Wall -Wextra -Wpedantic -g -I src/
PROFILE := release
CRYPTO_PROVIDER := aws-lc-rs
CERT_COMPRESSION := false
FIPS := false
PREFIX=/usr/local

ifeq ($(PROFILE), debug)
Expand All @@ -39,6 +40,11 @@ ifeq ($(CERT_COMPRESSION), true)
CARGOFLAGS += --features cert_compression
endif

# See https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html
ifeq ($(FIPS), true)
CARGOFLAGS += --features fips
endif

all: target/client target/server

integration: all
Expand Down
3 changes: 2 additions & 1 deletion cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ include = ["rustls_tls_version"]
"feature = read_buf" = "DEFINE_READ_BUF"
"feature = aws-lc-rs" = "DEFINE_AWS_LC_RS"
"feature = ring" = "DEFINE_RING"
"feature = fips" = "DEFINE_FIPS"

[parse.expand]
crates = ["rustls-ffi"]
features = ["read_buf", "aws-lc-rs", "ring"]
features = ["read_buf", "aws-lc-rs", "ring", "fips"]
13 changes: 13 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,19 @@ impl rustls_client_config_builder {
}

impl rustls_client_config {
/// Returns true if a `rustls_connection` created from the `rustls_client_config` will
/// operate in FIPS mode.
///
/// This is different from `rustls_crypto_provider_fips` which is concerned
/// only with cryptography, whereas this also covers TLS-level configuration that NIST
/// recommends, as well as ECH HPKE suites if applicable.
#[no_mangle]
pub extern "C" fn rustls_client_config_fips(config: *const rustls_client_config) -> bool {
ffi_panic_boundary! {
try_ref_from_ptr!(config).fips()
}
}

/// "Free" a `rustls_client_config` previously returned from
/// `rustls_client_config_builder_build`.
///
Expand Down
17 changes: 17 additions & 0 deletions src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,23 @@ impl rustls_connection {
}
}

/// Returns true if the `rustls_connection` was made with a `rustls_client_config`
/// or `rustls_server_config` that is FIPS compatible.
///
/// This is different from `rustls_crypto_provider_fips` which is concerned
/// only with cryptography, whereas this also covers TLS-level configuration that NIST
/// recommends, as well as ECH HPKE suites if applicable.
#[no_mangle]
pub extern "C" fn rustls_connection_fips(conn: *const rustls_connection) -> bool {
ffi_panic_boundary! {
let conn = try_ref_from_ptr!(conn);
match &conn.conn {
rustls::Connection::Client(c) => c.fips(),
rustls::Connection::Server(c) => c.fips(),
}
}
}

/// Free a rustls_connection. Calling with NULL is fine.
/// Must not be called twice with the same value.
#[no_mangle]
Expand Down
33 changes: 33 additions & 0 deletions src/crypto_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,26 @@ pub extern "C" fn rustls_aws_lc_rs_crypto_provider() -> *const rustls_crypto_pro
}
}

/// Return a `rustls_crypto_provider` that uses FIPS140-3 approved cryptography.
///
/// Using this function expresses in your code that you require FIPS-approved cryptography,
/// and will not compile if you make a mistake with cargo features.
///
/// See the upstream [rustls FIPS documentation][FIPS] for more information.
///
/// The caller owns the returned `rustls_crypto_provider` and must free it using
/// `rustls_crypto_provider_free`.
///
/// [FIPS]: https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html
#[no_mangle]
#[cfg(feature = "fips")]
pub extern "C" fn rustls_default_fips_provider() -> *const rustls_crypto_provider {
ffi_panic_boundary! {
Arc::into_raw(Arc::new(rustls::crypto::default_fips_provider()))
as *const rustls_crypto_provider
}
}

/// Retrieve a pointer to the process default `rustls_crypto_provider`.
///
/// This may return `NULL` if no process default provider has been set using
Expand Down Expand Up @@ -364,6 +384,19 @@ pub extern "C" fn rustls_crypto_provider_random(
}
}

/// Returns true if the `rustls_crypto_provider` is operating in FIPS mode.
///
/// This covers only the cryptographic parts of FIPS approval. There are also
/// TLS protocol-level recommendations made by NIST. You should prefer to call
/// `rustls_client_config_fips` or `rustls_server_config_fips` which take these
/// into account.
#[no_mangle]
pub extern "C" fn rustls_crypto_provider_fips(provider: *const rustls_crypto_provider) -> bool {
ffi_panic_boundary! {
try_ref_from_ptr!(provider).fips()
}
}

/// Frees the `rustls_crypto_provider`.
///
/// Calling with `NULL` is fine.
Expand Down
57 changes: 57 additions & 0 deletions src/rustls.h
Original file line number Diff line number Diff line change
Expand Up @@ -1758,6 +1758,16 @@ rustls_result rustls_client_config_builder_build(struct rustls_client_config_bui
*/
void rustls_client_config_builder_free(struct rustls_client_config_builder *config);

/**
* Returns true if a `rustls_connection` created from the `rustls_client_config` will
* operate in FIPS mode.
*
* This is different from `rustls_crypto_provider_fips` which is concerned
* only with cryptography, whereas this also covers TLS-level configuration that NIST
* recommends, as well as ECH HPKE suites if applicable.
*/
bool rustls_client_config_fips(const struct rustls_client_config *config);

/**
* "Free" a `rustls_client_config` previously returned from
* `rustls_client_config_builder_build`.
Expand Down Expand Up @@ -2047,6 +2057,16 @@ rustls_result rustls_connection_read_2(struct rustls_connection *conn,
size_t *out_n);
#endif

/**
* Returns true if the `rustls_connection` was made with a `rustls_client_config`
* or `rustls_server_config` that is FIPS compatible.
*
* This is different from `rustls_crypto_provider_fips` which is concerned
* only with cryptography, whereas this also covers TLS-level configuration that NIST
* recommends, as well as ECH HPKE suites if applicable.
*/
bool rustls_connection_fips(const struct rustls_connection *conn);

/**
* Free a rustls_connection. Calling with NULL is fine.
* Must not be called twice with the same value.
Expand Down Expand Up @@ -2172,6 +2192,23 @@ const struct rustls_crypto_provider *rustls_ring_crypto_provider(void);
const struct rustls_crypto_provider *rustls_aws_lc_rs_crypto_provider(void);
#endif

#if defined(DEFINE_FIPS)
/**
* Return a `rustls_crypto_provider` that uses FIPS140-3 approved cryptography.
*
* Using this function expresses in your code that you require FIPS-approved cryptography,
* and will not compile if you make a mistake with cargo features.
*
* See the upstream [rustls FIPS documentation][FIPS] for more information.
*
* The caller owns the returned `rustls_crypto_provider` and must free it using
* `rustls_crypto_provider_free`.
*
* [FIPS]: https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html
*/
const struct rustls_crypto_provider *rustls_default_fips_provider(void);
#endif

/**
* Retrieve a pointer to the process default `rustls_crypto_provider`.
*
Expand Down Expand Up @@ -2233,6 +2270,16 @@ rustls_result rustls_crypto_provider_random(const struct rustls_crypto_provider
uint8_t *buff,
size_t len);

/**
* Returns true if the `rustls_crypto_provider` is operating in FIPS mode.
*
* This covers only the cryptographic parts of FIPS approval. There are also
* TLS protocol-level recommendations made by NIST. You should prefer to call
* `rustls_client_config_fips` or `rustls_server_config_fips` which take these
* into account.
*/
bool rustls_crypto_provider_fips(const struct rustls_crypto_provider *provider);

/**
* Frees the `rustls_crypto_provider`.
*
Expand Down Expand Up @@ -2497,6 +2544,16 @@ rustls_result rustls_server_config_builder_set_certified_keys(struct rustls_serv
rustls_result rustls_server_config_builder_build(struct rustls_server_config_builder *builder,
const struct rustls_server_config **config_out);

/**
* Returns true if a `rustls_connection` created from the `rustls_server_config` will
* operate in FIPS mode.
*
* This is different from `rustls_crypto_provider_fips` which is concerned
* only with cryptography, whereas this also covers TLS-level configuration that NIST
* recommends, as well as ECH HPKE suites if applicable.
*/
bool rustls_server_config_fips(const struct rustls_server_config *config);

/**
* "Free" a rustls_server_config previously returned from
* rustls_server_config_builder_build.
Expand Down
13 changes: 13 additions & 0 deletions src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,19 @@ impl rustls_server_config_builder {
}

impl rustls_server_config {
/// Returns true if a `rustls_connection` created from the `rustls_server_config` will
/// operate in FIPS mode.
///
/// This is different from `rustls_crypto_provider_fips` which is concerned
/// only with cryptography, whereas this also covers TLS-level configuration that NIST
/// recommends, as well as ECH HPKE suites if applicable.
#[no_mangle]
pub extern "C" fn rustls_server_config_fips(config: *const rustls_server_config) -> bool {
ffi_panic_boundary! {
try_ref_from_ptr!(config).fips()
}
}

/// "Free" a rustls_server_config previously returned from
/// rustls_server_config_builder_build.
///
Expand Down
10 changes: 8 additions & 2 deletions tests/client_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,25 @@ fn client_server_integration() {
],
};

// CHACHA20 is not FIPS approved :)
#[cfg(not(feature = "fips"))]
let custom_ciphersuite = "TLS13_CHACHA20_POLY1305_SHA256";
#[cfg(feature = "fips")]
let custom_ciphersuite = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";

let custom_ciphersuites = TestCase {
name: "client/server with limited ciphersuites",
server_opts: ServerOptions {
valgrind: valgrind.clone(),
env: vec![("RUSTLS_CIPHERSUITE", "TLS13_CHACHA20_POLY1305_SHA256")],
env: vec![("RUSTLS_CIPHERSUITE", custom_ciphersuite)],
},
client_tests: vec![
ClientTest {
name: "limited ciphersuite, supported by server",
valgrind: valgrind.clone(),
env: vec![
("NO_CHECK_CERTIFICATE", "1"),
("RUSTLS_CIPHERSUITE", "TLS13_CHACHA20_POLY1305_SHA256"),
("RUSTLS_CIPHERSUITE", custom_ciphersuite),
],
expect_error: false,
},
Expand Down