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}