Skip to main content

boring_rustls_provider/
lib.rs

1//! A [`rustls`] [`CryptoProvider`] backed by [BoringSSL](https://github.com/cloudflare/boring).
2//!
3//! # Quick start
4//!
5//! ```no_run
6//! let config = rustls::ClientConfig::builder_with_provider(
7//!         boring_rustls_provider::provider().into(),
8//!     )
9//!     .with_safe_default_protocol_versions()
10//!     .unwrap()
11//!     .with_root_certificates(rustls::RootCertStore::empty())
12//!     .with_no_client_auth();
13//! ```
14//!
15//! # Features
16//!
17//! No features are enabled by default. The provider ships with TLS 1.3 support
18//! out of the box; additional capabilities are opt-in via feature flags:
19//!
20//! - **`fips`** — Build against FIPS-validated BoringSSL and restrict the
21//!   provider to FIPS-approved algorithms (SP 800-52r2). Implies `mlkem`.
22//! - **`mlkem`** — Enable the X25519MLKEM768 post-quantum hybrid key exchange
23//!   group.
24//! - **`tls12`** — Enable TLS 1.2 cipher suites.
25//! - **`logging`** — Enable debug logging via the [`log`](https://docs.rs/log)
26//!   crate.
27
28use std::sync::Arc;
29
30use helper::log_and_map;
31#[cfg(all(feature = "fips", feature = "log"))]
32use log::warn;
33use rustls::{
34    SupportedCipherSuite,
35    crypto::{CryptoProvider, GetRandomFailed, SupportedKxGroup},
36};
37use rustls_pki_types::PrivateKeyDer;
38
39mod aead;
40mod hash;
41mod helper;
42mod hkdf;
43mod hmac;
44mod kx;
45#[cfg(feature = "tls12")]
46mod prf;
47/// Private key loading and TLS signing operations.
48pub mod sign;
49/// TLS 1.2 cipher suite definitions (requires the `tls12` feature).
50#[cfg(feature = "tls12")]
51pub mod tls12;
52/// TLS 1.3 cipher suite definitions.
53pub mod tls13;
54/// Signature verification algorithms for certificate validation.
55pub mod verify;
56
57/// Returns a [`CryptoProvider`] with the default set of cipher suites and
58/// key exchange groups.
59///
60/// When the `fips` feature is enabled the provider is restricted to
61/// FIPS-approved algorithms and will **panic** if the underlying BoringSSL
62/// library is not running in FIPS mode.
63pub fn provider() -> CryptoProvider {
64    #[cfg(feature = "fips")]
65    {
66        if !boring::fips::enabled() {
67            panic!(
68                "boring-rustls-provider is built with the 'fips' feature, but the underlying \
69                BoringSSL library is not in FIPS mode."
70            );
71        }
72        provider_with_ciphers(ALL_FIPS_CIPHER_SUITES.to_vec())
73    }
74    #[cfg(not(feature = "fips"))]
75    {
76        provider_with_ciphers(ALL_CIPHER_SUITES.to_vec())
77    }
78}
79
80/// Returns a [`CryptoProvider`] using the given cipher suites.
81///
82/// When the `fips` feature is enabled, any non-FIPS cipher suites in
83/// `ciphers` are silently filtered out.
84pub fn provider_with_ciphers(ciphers: Vec<rustls::SupportedCipherSuite>) -> CryptoProvider {
85    #[cfg(feature = "fips")]
86    let ciphers = {
87        let original_len = ciphers.len();
88        let filtered = ciphers
89            .into_iter()
90            .filter(|suite| ALL_FIPS_CIPHER_SUITES.contains(suite))
91            .collect::<Vec<_>>();
92
93        if filtered.len() != original_len {
94            #[cfg(feature = "log")]
95            warn!(
96                "filtered {} non-FIPS cipher suite(s) from provider_with_ciphers",
97                original_len - filtered.len()
98            );
99        }
100
101        filtered
102    };
103
104    CryptoProvider {
105        cipher_suites: ciphers,
106        #[cfg(feature = "fips")]
107        kx_groups: ALL_FIPS_KX_GROUPS.to_vec(),
108        #[cfg(not(feature = "fips"))]
109        kx_groups: ALL_KX_GROUPS.to_vec(),
110        #[cfg(feature = "fips")]
111        signature_verification_algorithms: verify::ALL_FIPS_ALGORITHMS,
112        #[cfg(not(feature = "fips"))]
113        signature_verification_algorithms: verify::ALL_ALGORITHMS,
114        secure_random: &Provider,
115        key_provider: &Provider,
116    }
117}
118
119#[derive(Debug)]
120struct Provider;
121
122impl rustls::crypto::SecureRandom for Provider {
123    fn fill(&self, bytes: &mut [u8]) -> Result<(), rustls::crypto::GetRandomFailed> {
124        boring::rand::rand_bytes(bytes).map_err(|e| log_and_map("rand_bytes", e, GetRandomFailed))
125    }
126
127    fn fips(&self) -> bool {
128        cfg!(feature = "fips")
129    }
130}
131
132impl rustls::crypto::KeyProvider for Provider {
133    fn load_private_key(
134        &self,
135        key_der: PrivateKeyDer<'static>,
136    ) -> Result<Arc<dyn rustls::sign::SigningKey>, rustls::Error> {
137        sign::BoringPrivateKey::try_from(key_der).map(|x| Arc::new(x) as _)
138    }
139
140    fn fips(&self) -> bool {
141        cfg!(feature = "fips")
142    }
143}
144
145#[cfg(feature = "fips")]
146static ALL_FIPS_CIPHER_SUITES: &[SupportedCipherSuite] = &[
147    SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384),
148    SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256),
149    #[cfg(feature = "tls12")]
150    SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES256_GCM_SHA384),
151    #[cfg(feature = "tls12")]
152    SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES256_GCM_SHA384),
153    #[cfg(feature = "tls12")]
154    SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES128_GCM_SHA256),
155    #[cfg(feature = "tls12")]
156    SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES128_GCM_SHA256),
157];
158
159#[cfg(not(feature = "fips"))]
160static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[
161    SupportedCipherSuite::Tls13(&tls13::CHACHA20_POLY1305_SHA256),
162    SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384),
163    SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256),
164    #[cfg(feature = "tls12")]
165    SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256),
166    #[cfg(feature = "tls12")]
167    SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256),
168    #[cfg(feature = "tls12")]
169    SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES256_GCM_SHA384),
170    #[cfg(feature = "tls12")]
171    SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES256_GCM_SHA384),
172    #[cfg(feature = "tls12")]
173    SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES128_GCM_SHA256),
174    #[cfg(feature = "tls12")]
175    SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES128_GCM_SHA256),
176];
177
178/// Allowed KX groups for FIPS per [SP 800-52r2](https://doi.org/10.6028/NIST.SP.800-52r2),
179/// aligned with boring's `fips202205` compliance policy.
180///
181/// The `fips` feature implies `mlkem`, so X25519MLKEM768 is always
182/// available and preferred in FIPS mode.
183#[cfg(feature = "fips")]
184static ALL_FIPS_KX_GROUPS: &[&dyn SupportedKxGroup] = &[
185    &kx::X25519MlKem768 as _, // PQ hybrid preferred
186    &kx::Secp256r1 as _,      // P-256
187    &kx::Secp384r1 as _,      // P-384
188];
189
190/// All supported KX groups, ordered by preference.
191///
192/// Matches boring's default supported group list exactly:
193/// X25519MLKEM768 (when mlkem enabled), X25519, P-256, P-384.
194#[cfg(all(not(feature = "fips"), feature = "mlkem"))]
195static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[
196    &kx::X25519MlKem768 as _, // PQ hybrid preferred
197    &kx::X25519 as _,
198    &kx::Secp256r1 as _,
199    &kx::Secp384r1 as _,
200];
201
202/// See [`ALL_KX_GROUPS`] (mlkem variant).
203#[cfg(not(any(feature = "fips", feature = "mlkem")))]
204static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] =
205    &[&kx::X25519 as _, &kx::Secp256r1 as _, &kx::Secp384r1 as _];