Skip to main content

ic_rustls/
lib.rs

1//! IronCrypto as a [rustls] `CryptoProvider`.
2//!
3//! ```
4//! # fn main() -> Result<(), rustls::Error> {
5//! let roots = rustls::RootCertStore::empty();
6//! let config = rustls::ClientConfig::builder_with_provider(ic_rustls::arc_provider())
7//!     .with_safe_default_protocol_versions()?
8//!     .with_root_certificates(roots)
9//!     .with_no_client_auth();
10//! # let _ = config;
11//! # Ok(())
12//! # }
13//! ```
14//!
15//! Or install it once, for every rustls configuration in the process:
16//!
17//! ```no_run
18//! ic_rustls::provider()
19//!     .install_default()
20//!     .expect("a provider was already installed in this process");
21//! ```
22//!
23//! # This crate has a third-party dependency, and it is the only one that does
24//!
25//! Every other crate in this workspace depends on nothing outside it. That is
26//! asserted on each build by `scripts/no-third-party.sh`, and the SBOM,
27//! CWE-1104 and T1195.001 all rest on it.
28//!
29//! A rustls provider cannot: it exists to implement rustls's traits, so it must
30//! depend on rustls, and rustls brings `rustls-pki-types`, `rustls-webpki`,
31//! `subtle`, `untrusted`, `once_cell` and `zeroize` with it -- seven crates in
32//! total, which is what `scripts/no-third-party.sh` allows by name and
33//! `scripts/advisories.sh` holds to a version floor. Rather than weaken the check, the boundary
34//! is drawn here. This crate is excluded by name, the exclusion is one line
35//! with a reason beside it, and everything cryptographic stays on the other
36//! side: `ic-core`, `ic-hash`, `ic-mac`, `ic-cipher`, `ic-drbg`, `ic-ec` and
37//! `ic-pkix` are unchanged and still depend on nothing.
38//!
39//! So the guarantee narrows honestly instead of quietly. If you need it whole,
40//! do not depend on this crate; the algorithms are reachable directly.
41//!
42//! # What is provided
43//!
44//! | | |
45//! |---|---|
46//! | AEAD | AES-128-GCM, AES-256-GCM and ChaCha20-Poly1305, for TLS 1.3 and TLS 1.2 |
47//! | Hash | SHA-256, SHA-384 |
48//! | MAC | HMAC-SHA256, HMAC-SHA384 |
49//! | KDF | HKDF, as rustls's `HkdfUsingHmac` over the above |
50//! | Signatures | ECDSA P-256/SHA-256 and P-384/SHA-384; Ed25519; RSA PKCS#1 v1.5 and PSS over SHA-256/384/512. All verified and produced |
51//! | Key exchange | X25519, ECDH P-256, ECDH P-384 |
52//! | Randomness | SP 800-90A HMAC\_DRBG, seeded from the OS |
53//! | QUIC | Packet and header protection for all three AEADs, RFC 9001 |
54//!
55//! HKDF is rustls's own extract-and-expand over IronCrypto's HMAC, which is the
56//! right split: HKDF is a construction and HMAC is the primitive. The result is
57//! checked against RFC 5869 in the `hmac` module's tests, so the composition is verified
58//! and not just assumed.
59//!
60//! # What is not provided
61//!
62//! - **RSA below 2048 bits.** Refused, deliberately, when verifying and when
63//!   loading a key to sign with. See `crate::verify`.
64//! - **The mismatched ECDSA pairings.** A P-256 key signed with SHA-384, or
65//!   the reverse. See `crate::verify`.
66//! - **FIPS validation.** Every `fips()` in this crate returns `false`, because
67//!   rustls is asking about a certificate and IronCrypto holds none.
68//!
69//! [rustls]: https://docs.rs/rustls
70
71#![forbid(unsafe_code)]
72#![deny(missing_docs)]
73#![warn(clippy::all)]
74
75extern crate alloc;
76
77mod aead;
78mod hash;
79mod hmac;
80mod kx;
81mod quic;
82mod sign;
83mod verify;
84
85use alloc::sync::Arc;
86
87use rustls::crypto::{CryptoProvider, SupportedKxGroup, WebPkiSupportedAlgorithms};
88use rustls::pki_types::SignatureVerificationAlgorithm;
89use rustls::{SignatureScheme, SupportedCipherSuite};
90
91pub mod random;
92pub mod suites;
93
94/// The provider.
95///
96/// Cipher suites are listed strongest first, which is the order rustls offers
97/// them in. AES-256 before AES-128 on the grounds that both are cheap where
98/// there is hardware for them and the difference matters more than the cost.
99pub fn provider() -> CryptoProvider {
100    CryptoProvider {
101        cipher_suites: default_cipher_suites().to_vec(),
102        kx_groups: default_kx_groups().to_vec(),
103        signature_verification_algorithms: SUPPORTED_SIG_ALGS,
104        secure_random: &random::Random,
105        key_provider: &sign::Keys,
106    }
107}
108
109/// The cipher suites this provider offers, strongest first.
110pub fn default_cipher_suites() -> &'static [SupportedCipherSuite] {
111    suites::ALL
112}
113
114/// The key exchange groups this provider offers.
115///
116/// X25519 first: it is fast, has no point validation to get wrong, and no
117/// invalid-curve attack surface. The NIST curves follow for peers that require
118/// them.
119pub fn default_kx_groups() -> &'static [&'static dyn SupportedKxGroup] {
120    KX_GROUPS
121}
122
123static KX_GROUPS: &[&dyn SupportedKxGroup] = &[&kx::X25519, &kx::SECP256R1, &kx::SECP384R1];
124
125/// Signature verification algorithms, for certificate chains and for the
126/// handshake.
127/// `all` is what certificate chains are verified with, and `mapping` is what
128/// the handshake signature is looked up in. Both are needed: a chain signed
129/// with PKCS#1 v1.5 can carry a key that then signs the handshake with PSS, and
130/// TLS 1.3 requires exactly that combination.
131pub static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms {
132    all: &[
133        &verify::ECDSA_P256_SHA256 as &dyn SignatureVerificationAlgorithm,
134        &verify::ECDSA_P384_SHA384 as &dyn SignatureVerificationAlgorithm,
135        &verify::ED25519 as &dyn SignatureVerificationAlgorithm,
136        &verify::RSA_PKCS1_SHA256 as &dyn SignatureVerificationAlgorithm,
137        &verify::RSA_PKCS1_SHA384 as &dyn SignatureVerificationAlgorithm,
138        &verify::RSA_PKCS1_SHA512 as &dyn SignatureVerificationAlgorithm,
139        &verify::RSA_PSS_SHA256 as &dyn SignatureVerificationAlgorithm,
140        &verify::RSA_PSS_SHA384 as &dyn SignatureVerificationAlgorithm,
141        &verify::RSA_PSS_SHA512 as &dyn SignatureVerificationAlgorithm,
142    ],
143    mapping: &[
144        (
145            SignatureScheme::ECDSA_NISTP384_SHA384,
146            &[&verify::ECDSA_P384_SHA384 as &dyn SignatureVerificationAlgorithm],
147        ),
148        (
149            SignatureScheme::ECDSA_NISTP256_SHA256,
150            &[&verify::ECDSA_P256_SHA256 as &dyn SignatureVerificationAlgorithm],
151        ),
152        (
153            SignatureScheme::ED25519,
154            &[&verify::ED25519 as &dyn SignatureVerificationAlgorithm],
155        ),
156        (
157            SignatureScheme::RSA_PSS_SHA512,
158            &[&verify::RSA_PSS_SHA512 as &dyn SignatureVerificationAlgorithm],
159        ),
160        (
161            SignatureScheme::RSA_PSS_SHA384,
162            &[&verify::RSA_PSS_SHA384 as &dyn SignatureVerificationAlgorithm],
163        ),
164        (
165            SignatureScheme::RSA_PSS_SHA256,
166            &[&verify::RSA_PSS_SHA256 as &dyn SignatureVerificationAlgorithm],
167        ),
168        (
169            SignatureScheme::RSA_PKCS1_SHA512,
170            &[&verify::RSA_PKCS1_SHA512 as &dyn SignatureVerificationAlgorithm],
171        ),
172        (
173            SignatureScheme::RSA_PKCS1_SHA384,
174            &[&verify::RSA_PKCS1_SHA384 as &dyn SignatureVerificationAlgorithm],
175        ),
176        (
177            SignatureScheme::RSA_PKCS1_SHA256,
178            &[&verify::RSA_PKCS1_SHA256 as &dyn SignatureVerificationAlgorithm],
179        ),
180    ],
181};
182
183/// The provider, ready to hand to a rustls builder.
184pub fn arc_provider() -> Arc<CryptoProvider> {
185    Arc::new(provider())
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    /// Everything the module documentation promises must actually be offered.
193    ///
194    /// A provider that silently omits a suite does not fail; it negotiates
195    /// something else, or nothing, and the reason is several layers away from
196    /// whoever has to debug it.
197    #[test]
198    fn the_provider_offers_what_it_says_it_does() {
199        let p = provider();
200
201        // Nine suites: three AEADs for TLS 1.3, and the same three for TLS 1.2
202        // once with an ECDSA certificate and once with an RSA one. The literal
203        // is here to catch a *removal*, which the list below cannot: dropping a
204        // suite and its expectation together would otherwise pass.
205        assert_eq!(p.cipher_suites.len(), 9, "{:?}", p.cipher_suites);
206        let names: alloc::vec::Vec<_> = p
207            .cipher_suites
208            .iter()
209            .map(|s| alloc::format!("{:?}", s.suite()))
210            .collect();
211        for want in [
212            "TLS13_AES_256_GCM_SHA384",
213            "TLS13_AES_128_GCM_SHA256",
214            "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
215            "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
216            "TLS13_CHACHA20_POLY1305_SHA256",
217            "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
218            "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
219            "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
220            "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
221        ] {
222            assert!(
223                names.iter().any(|n| n == want),
224                "{want} is missing: {names:?}"
225            );
226        }
227
228        // Three key exchange groups, X25519 preferred.
229        assert_eq!(p.kx_groups.len(), 3);
230        assert_eq!(p.kx_groups[0].name(), rustls::NamedGroup::X25519);
231
232        // Two ECDSA pairings, Ed25519, and six RSA ones, with a mapping each.
233        assert_eq!(p.signature_verification_algorithms.all.len(), 9);
234        assert_eq!(p.signature_verification_algorithms.mapping.len(), 9);
235    }
236
237    /// Nothing in the provider may report FIPS validation.
238    ///
239    /// rustls surfaces this to applications, some of which gate behaviour on
240    /// it. IronCrypto holds no CMVP certificate, so every answer here is false
241    /// and must stay false -- the same rule
242    /// `ic_ontology::runtime::has("fips-validated")` follows.
243    #[test]
244    fn nothing_claims_fips_validation() {
245        let p = provider();
246        assert!(!p.fips(), "the provider as a whole claims validation");
247
248        let mut checked = 0;
249        for suite in &p.cipher_suites {
250            assert!(!suite.fips(), "{:?} claims validation", suite.suite());
251            checked += 1;
252        }
253        for group in &p.kx_groups {
254            assert!(!group.fips(), "{:?} claims validation", group.name());
255            checked += 1;
256        }
257        for alg in p.signature_verification_algorithms.all {
258            assert!(!alg.fips(), "a signature algorithm claims validation");
259            checked += 1;
260        }
261        // Nine suites, three key exchange groups, nine signature algorithms.
262        assert!(checked >= 21, "only {checked} components examined");
263    }
264
265    /// The suites must name the hash and HMAC this crate provides, or the key
266    /// schedule is someone else's.
267    #[test]
268    fn the_suites_use_this_provider_for_the_key_schedule() {
269        use rustls::crypto::hash::HashAlgorithm;
270
271        for suite in suites::ALL {
272            let hash = match suite {
273                SupportedCipherSuite::Tls13(t) => t.common.hash_provider,
274                SupportedCipherSuite::Tls12(t) => t.common.hash_provider,
275            };
276            assert!(
277                matches!(
278                    hash.algorithm(),
279                    HashAlgorithm::SHA256 | HashAlgorithm::SHA384
280                ),
281                "{:?} uses an unexpected hash",
282                suite.suite()
283            );
284            assert!(!hash.fips());
285        }
286    }
287}