Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ jobs:
run: cargo clippy --all-targets -- -D warnings
- name: cargo clippy --no-default-features (warnings)
run: cargo clippy --no-default-features --all-targets -- -D warnings
- name: cargo clippy --features fips (warnings)
run: cargo clippy --features fips --all-targets -- -D warnings
- name: cargo doc (warnings)
run: cargo doc --no-deps
env:
RUSTDOCFLAGS: -D warnings

coverage:
name: Coverage
Expand Down
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ pkcs1 = { version = "0.7.5", features = ["std"] }
pkcs8 = "0.11.0"
rustls = { version = "0.23.20", default-features = false, features = ["std"] }
sec1 = "0.8.1"
zeroize = "1.8.1"

[target.'cfg(windows)'.dependencies]
windows = { version = "0.61.3", features = [
"Win32_Security_Cryptography",
"Win32_System_WinRT",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this and are you sure it was unused?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Reverted the Cargo.toml dependency reshuffle and WinRT feature removal in 8f49924. That change was not necessary for this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Direct answer: I moved windows to a target-specific dependency and removed Win32_System_WinRT because local grep showed no source references to WinRT APIs and I was trying to reduce non-Windows dependency exposure. That was too broad for this PR, and I did not prove the feature was unnecessary across generated bindings/platform behavior. I reverted the Cargo.toml change in 8f49924.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Investigated more deeply and removed it in 17b2971. Evidence: repo search has no WinRT, System_WinRT, or Win32_System references outside the old Cargo feature; after removal, cargo tree -e features -i windows --target x86_64-pc-windows-msvc no longer contains Win32_System or Win32_System_WinRT; and the Windows-target validation set passes (cargo check default/no-default/fips, clippy default/no-default/fips with -D warnings, and denied-warning rustdoc). So this feature was unused by the crate and safe to remove.

] }
zeroize = "1.8.1"

[features]
default = ["tls12"]
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ Full test coverage requires Windows because the provider calls Windows CNG APIs:
cargo test
```

The default test suite is hermetic. The crates.io interoperability test is ignored by default because it requires live network access; run it explicitly when needed:

```bash
cargo test test_to_internet -- --ignored
```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessary, we can assume test runners have network access. Simplify the tests and remove this part of the docs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Removed the ignored-test docs and restored the crates.io interoperability test to the default test set in 8f49924.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Direct answer: this section was not necessary. I added it because I assumed default tests should avoid live network dependencies, but in this repo/CI we can assume network access for test runners. I removed the docs and restored the test to the default suite in 8f49924.


This crate only builds for Windows targets. From non-Windows hosts, run check and documentation workflows with an explicit Windows target:

```bash
cargo check --target x86_64-pc-windows-msvc
RUSTDOCFLAGS='-D warnings' cargo doc --no-deps --target x86_64-pc-windows-msvc
```

Run formatting checks before submitting changes:

```bash
Expand Down
13 changes: 13 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/)
// Copyright 2026 Datadog, Inc.

fn main() {
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
panic!(
"rustls-cng-crypto uses Windows CNG APIs and only builds for Windows targets; \
use --target x86_64-pc-windows-msvc when checking from non-Windows hosts"
);
}
}
36 changes: 15 additions & 21 deletions src/fips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,6 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/)
// Copyright 2026 Datadog, Inc.

//! # FIPS support
//!
//! To use rustls with this crate in FIPS mode, perform the following actions.
//!
//! ## 1. Enable FIPS mode for Windows
//!
//! See [Microsoft documentation](https://learn.microsoft.com/en-us/windows/security/security-foundations/certification/fips-140-validation).
//!
//! ## 2. Enable the `fips` feature, or explicitly use the [crate::fips_provider()] function
//!
//! The fips feature changes the behaviour of [crate::default_provider()] to use FIPS-approved cipher suites and key exchange groups.
//! Or you can explicitly use the [crate::fips_provider()] function to create a provider with FIPS-approved cipher suites and key exchange groups.
//! If Windows is not running in FIPS mode, the provider will be empty.
//!
//! ## 3. Specify `require_ems` when constructing [rustls::ClientConfig] or [rustls::ServerConfig]
//!
//! See [rustls documentation](https://docs.rs/rustls/latest/rustls/client/struct.ClientConfig.html#structfield.require_ems) for rationale.
//!
//! ## 4. Validate the FIPS status of your ClientConfig or ServerConfig at runtime
//! See [rustls documentation on FIPS](https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html#3-validate-the-fips-status-of-your-clientconfigserverconfig-at-run-time).

use rustls::crypto::CryptoProvider;
use windows::Win32::Security::Cryptography::BCryptGetFipsAlgorithmMode;

Expand All @@ -39,6 +18,21 @@ pub(crate) fn enabled() -> bool {

/// Returns a CNG-based [`CryptoProvider`] using FIPS-approved cipher suites and key exchange groups.
///
/// To use rustls with this provider in FIPS mode:
///
/// 1. Enable FIPS mode for Windows. See Microsoft's
/// [FIPS 140 Validation](https://learn.microsoft.com/en-us/windows/security/security-foundations/certification/fips-140-validation)
/// documentation.
/// 2. Enable this crate's `fips` feature, or explicitly use [`crate::fips_provider()`]. The `fips`
/// feature changes [`crate::default_provider()`] to use FIPS-approved cipher suites and key
/// exchange groups.
/// 3. Specify `require_ems` when constructing [`rustls::ClientConfig`] or
/// [`rustls::ServerConfig`]. See the rustls
/// [FIPS manual](https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html)
/// for rationale.
/// 4. Validate the FIPS status of your `ClientConfig` or `ServerConfig` at runtime. See the rustls
/// [FIPS status documentation](https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html#3-validate-the-fips-status-of-your-clientconfigserverconfig-at-run-time).
///
/// Usage requires that Windows is running in FIPS mode, otherwise the provider will be empty.
pub fn provider() -> CryptoProvider {
CryptoProvider {
Expand Down
7 changes: 7 additions & 0 deletions src/kx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/)
// Copyright 2026 Datadog, Inc.

#[cfg(not(feature = "fips"))]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are all these config gates necessary? How was this working before?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Removed the extra cfg(not(feature = "fips")) gates in 8f49924. To keep --features fips clippy clean without those gates, fips_provider() now uses kx::default_kx_groups() and then filters to FIPS-approved groups.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Direct answer: no, all those gates were not necessary. It worked before because the default KX helper code was compiled under --features fips but not used, producing dead-code warnings only when we added FIPS clippy with -D warnings. Instead of cfg-gating the helper code everywhere, I changed fips_provider() to call kx::default_kx_groups() and then filter to FIPS groups, so the existing helper path remains used and FIPS clippy stays clean. That replacement is in 8f49924.

use once_cell::sync::Lazy;
use rustls::crypto::{ActiveKeyExchange, SharedSecret, SupportedKxGroup};
use rustls::{Error, NamedGroup};
Expand All @@ -27,6 +28,7 @@ const MAX_SECRET_SIZE: usize = 48;
/// * [SECP256R1]
///
pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[X25519, SECP256R1, SECP384R1];
#[cfg(not(feature = "fips"))]
static DEFAULT_KX_GROUPS: Lazy<Vec<&'static dyn SupportedKxGroup>> = Lazy::new(|| {
ALL_KX_GROUPS
.iter()
Expand Down Expand Up @@ -75,10 +77,12 @@ impl KxGroup {
}
}

#[cfg(not(feature = "fips"))]
fn usable_kx_group(kx_group: &dyn SupportedKxGroup) -> bool {
kx_group.name() != NamedGroup::X25519 || cng_supports_x25519()
}

#[cfg(not(feature = "fips"))]
fn cng_supports_x25519() -> bool {
// Windows CNG's Curve25519 public-key import behavior differs by OS version. Windows Server
// 2022 accepts the X25519 Wycheproof `u = 4` vector, but Windows Server 2025 rejects it with
Expand Down Expand Up @@ -112,6 +116,7 @@ pub const SECP256R1: &dyn SupportedKxGroup = &KxGroup::SECP256R1;
pub const SECP384R1: &dyn SupportedKxGroup = &KxGroup::SECP384R1;

/// Returns key exchange groups usable by the host CNG implementation.
#[cfg(not(feature = "fips"))]
pub fn default_kx_groups() -> Vec<&'static dyn SupportedKxGroup> {
DEFAULT_KX_GROUPS.clone()
}
Expand Down Expand Up @@ -282,6 +287,7 @@ mod test {

use crate::{keys::import_ecdh_private_key, kx::EcKeyExchange};

#[cfg(not(feature = "fips"))]
#[test]
fn default_kx_groups_match_cng_x25519_support() {
let advertises_x25519 = super::default_kx_groups()
Expand Down Expand Up @@ -325,6 +331,7 @@ mod test {
}
}

#[cfg(not(feature = "fips"))]
#[test]
fn x25519() {
if !super::cng_supports_x25519() {
Expand Down
49 changes: 45 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
//! * `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`
//! * `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`
//! * `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256`
//! * `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
//! * `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`
//! * `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
//! * `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`
//!
//! ## Supported Key Exchanges
Expand Down Expand Up @@ -53,7 +53,31 @@
//!
//! # Features
//! - `tls12`: Enables TLS 1.2 cipher suites. Enabled by default.
//! - `fips`: Changes the default provider to use FIPS-approved cipher suites and key exchange groups. See [fips].
//! - `fips`: Changes the default provider to use FIPS-approved cipher suites and key exchange groups.
//! See [`fips_provider()`] and [FIPS support](#fips-support).
//!
//! ## FIPS support
//!
//! To use rustls with this crate in FIPS mode:
//!
//! 1. Enable FIPS mode for Windows. See Microsoft's
//! [FIPS 140 Validation](https://learn.microsoft.com/en-us/windows/security/security-foundations/certification/fips-140-validation)
//! documentation.
//! 2. Enable this crate's `fips` feature, or explicitly use [`fips_provider()`]. The `fips`
//! feature changes [`default_provider()`] to use FIPS-approved cipher suites and key exchange
//! groups. If Windows is not running in FIPS mode, the provider will be empty.
//! 3. Specify `require_ems` when constructing [`rustls::ClientConfig`] or
//! [`rustls::ServerConfig`]. See the rustls
//! [FIPS manual](https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html)
//! for rationale.
//! 4. Validate the FIPS status of your `ClientConfig` or `ServerConfig` at runtime. See the rustls
//! [FIPS status documentation](https://docs.rs/rustls/latest/rustls/manual/_06_fips/index.html#3-validate-the-fips-status-of-your-clientconfigserverconfig-at-run-time).
//!
//! ## Platform support
//!
//! This crate uses Windows CNG APIs and only builds for Windows targets. From non-Windows hosts,
//! run checks and documentation builds with an explicit Windows target such as
//! `--target x86_64-pc-windows-msvc`.
#![warn(missing_docs)]
use rustls::crypto::{CryptoProvider, GetRandomFailed, SupportedKxGroup};
use rustls::SupportedCipherSuite;
Expand All @@ -79,6 +103,21 @@ mod verify;

pub mod cipher_suite {
//! Supported cipher suites.
//!
//! ```rust
//! use rustls::CipherSuite;
//! use rustls_cng_crypto::{cipher_suite, custom_provider, kx_group};
//!
//! let provider = custom_provider(
//! vec![cipher_suite::TLS13_CHACHA20_POLY1305_SHA256],
//! vec![kx_group::SECP256R1],
//! );
//!
//! assert_eq!(
//! provider.cipher_suites[0].suite(),
//! CipherSuite::TLS13_CHACHA20_POLY1305_SHA256
//! );
//! ```

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this comment block for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Removed that rustdoc example block in 8f49924. The API export is still covered by the dedicated tests/api.rs test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Direct answer: that block was meant to make the new ChaCha20 public re-export show up in rustdoc/doctest coverage. It is overkill in module docs because tests/api.rs already exercises constructing a custom provider with TLS13_CHACHA20_POLY1305_SHA256. I removed the block in 8f49924.

#[cfg(feature = "tls12")]
pub use super::tls12::{
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
Expand All @@ -88,7 +127,9 @@ pub mod cipher_suite {
pub use super::tls12::{
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
};
pub use super::tls13::{TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384};
pub use super::tls13::{
TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS13_CHACHA20_POLY1305_SHA256,
};
}

pub use alg::ShutdownHandle;
Expand Down Expand Up @@ -190,8 +231,8 @@ pub fn custom_provider(
/// * `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`
/// * `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`
/// * `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256`
/// * `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
/// * `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`
/// * `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why'd you move this around? It seems like it was already ordered correctly

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Reverted the RSA TLS 1.2 cipher-suite doc ordering change in 8f49924.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[GPT 5.5] Direct answer: I moved it because my review notes said the prose preference order differed from the actual ALL_CIPHER_SUITES array order. Re-checking it here, that churn is not needed for this PR and it distracts from the docs/CI/API scope. I reverted the ordering change in 8f49924.

/// * `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256`
///
/// If the default `tls12` feature is disabled then the TLS 1.2 cipher suites will not be included.
Expand Down
21 changes: 21 additions & 0 deletions tests/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
//
// This product includes software developed at Datadog (https://www.datadoghq.com/)
// Copyright 2026 Datadog, Inc.

use rustls::CipherSuite;
use rustls_cng_crypto::{custom_provider, kx_group};

#[test]
fn tls13_chacha20_poly1305_sha256_is_available_for_custom_providers() {
let provider = custom_provider(
vec![rustls_cng_crypto::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256],
vec![kx_group::SECP256R1],
);

assert_eq!(provider.cipher_suites.len(), 1);
assert_eq!(
provider.cipher_suites[0].suite(),
CipherSuite::TLS13_CHACHA20_POLY1305_SHA256
);
}
1 change: 1 addition & 0 deletions tests/it.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ fn test_client_and_server(
rustls_cng_crypto::kx_group::SECP384R1,
CipherSuite::TLS13_AES_256_GCM_SHA384
)]
#[ignore = "requires live access to index.crates.io"]
fn test_to_internet(
#[case] suite: SupportedCipherSuite,
#[case] group: &'static dyn SupportedKxGroup,
Expand Down
Loading