tachyon_web/tls/
policy.rs1use rustls::SupportedProtocolVersion;
4use rustls::crypto::CryptoProvider;
5#[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
6use rustls::pki_types::{CertificateDer, PrivateKeyDer};
7use std::sync::Arc;
8
9#[derive(Clone)]
40pub struct TlsPolicy {
41 provider: Arc<CryptoProvider>,
42 versions: Vec<&'static SupportedProtocolVersion>,
43}
44
45impl std::fmt::Debug for TlsPolicy {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 f.debug_struct("TlsPolicy")
48 .field("tls13", &self.versions.contains(&&rustls::version::TLS13))
49 .field("tls12", &self.versions.contains(&&rustls::version::TLS12))
50 .finish_non_exhaustive()
51 }
52}
53
54impl TlsPolicy {
55 #[must_use]
62 pub fn hardened() -> Self {
63 Self::with_provider(hardened_provider())
64 }
65
66 #[must_use]
72 pub fn with_provider(provider: Arc<CryptoProvider>) -> Self {
73 Self {
74 provider,
75 versions: vec![&rustls::version::TLS13, &rustls::version::TLS12],
76 }
77 }
78
79 #[must_use]
81 pub fn tls13_only(mut self) -> Self {
82 self.versions = vec![&rustls::version::TLS13];
83 self
84 }
85
86 #[must_use]
88 pub fn provider(&self) -> Arc<CryptoProvider> {
89 self.provider.clone()
90 }
91
92 #[must_use]
94 pub fn versions(&self) -> &[&'static SupportedProtocolVersion] {
95 &self.versions
96 }
97
98 pub fn install_as_process_default(&self) {
108 let _ = (*self.provider).clone().install_default();
109 }
110
111 #[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
119 pub(crate) fn server_config_from_pem(
120 &self,
121 cert: &[u8],
122 key: &[u8],
123 ) -> Result<rustls::ServerConfig, std::io::Error> {
124 use rustls_pemfile::{certs, private_key};
125
126 let mut cert_reader = std::io::BufReader::new(cert);
127 let cert_chain: Vec<CertificateDer<'static>> = certs(&mut cert_reader)
128 .filter_map(std::result::Result::ok)
129 .collect();
130
131 let mut key_reader = std::io::BufReader::new(key);
132 let key_der: PrivateKeyDer<'static> = private_key(&mut key_reader)
133 .map_err(|e| {
134 std::io::Error::new(
135 std::io::ErrorKind::InvalidData,
136 format!("Failed to read private key: {e}"),
137 )
138 })?
139 .ok_or_else(|| {
140 std::io::Error::new(std::io::ErrorKind::NotFound, "No private key found in PEM")
141 })?;
142
143 let mut server_config = rustls::ServerConfig::builder_with_provider(self.provider())
144 .with_protocol_versions(&self.versions)
145 .map_err(|e| {
146 std::io::Error::new(
147 std::io::ErrorKind::InvalidData,
148 format!("TLS version configuration failed: {e}"),
149 )
150 })?
151 .with_no_client_auth()
152 .with_single_cert(cert_chain, key_der)
153 .map_err(|e| {
154 std::io::Error::new(
155 std::io::ErrorKind::InvalidInput,
156 format!("Invalid certificate or key: {e}"),
157 )
158 })?;
159
160 server_config.alpn_protocols = crate::server::alpn_protocols(false);
161 Ok(server_config)
162 }
163}
164
165impl Default for TlsPolicy {
166 fn default() -> Self {
167 Self::hardened()
168 }
169}
170
171fn hardened_provider() -> Arc<CryptoProvider> {
174 static DEFAULT_PROVIDER: std::sync::OnceLock<Arc<CryptoProvider>> = std::sync::OnceLock::new();
175 DEFAULT_PROVIDER
176 .get_or_init(|| {
177 let kx_groups = vec![
178 rustls::crypto::aws_lc_rs::kx_group::X25519MLKEM768,
179 rustls::crypto::aws_lc_rs::kx_group::SECP256R1MLKEM768,
180 rustls::crypto::aws_lc_rs::kx_group::MLKEM1024,
181 rustls::crypto::aws_lc_rs::kx_group::MLKEM768,
182 rustls::crypto::aws_lc_rs::kx_group::SECP384R1,
183 rustls::crypto::aws_lc_rs::kx_group::X25519,
184 rustls::crypto::aws_lc_rs::kx_group::SECP256R1,
185 ];
186
187 let cipher_suites = vec![
190 rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_256_GCM_SHA384,
192 rustls::crypto::aws_lc_rs::cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
193 rustls::crypto::aws_lc_rs::cipher_suite::TLS13_AES_128_GCM_SHA256,
194 rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
196 rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
197 rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
198 rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
199 rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
200 rustls::crypto::aws_lc_rs::cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
201 ];
202
203 Arc::new(CryptoProvider {
204 cipher_suites,
205 kx_groups,
206 ..rustls::crypto::aws_lc_rs::default_provider()
207 })
208 })
209 .clone()
210}
211
212#[cfg(test)]
213#[allow(clippy::expect_used)]
214mod tests {
215 use super::TlsPolicy;
216
217 #[test]
218 fn hardened_offers_both_tls_versions_by_default() {
219 let policy = TlsPolicy::hardened();
220 assert_eq!(policy.versions().len(), 2);
221 }
222
223 #[test]
224 fn tls13_only_restricts_to_a_single_version() {
225 let policy = TlsPolicy::hardened().tls13_only();
226 assert_eq!(policy.versions(), &[&rustls::version::TLS13]);
227 }
228
229 #[test]
230 fn default_matches_hardened() {
231 let default_versions = TlsPolicy::default().versions().len();
232 let hardened_versions = TlsPolicy::hardened().versions().len();
233 assert_eq!(default_versions, hardened_versions);
234 }
235
236 #[test]
237 fn debug_format_reports_negotiated_versions() {
238 let both = format!("{:?}", TlsPolicy::hardened());
239 assert!(both.contains("tls13: true"));
240 assert!(both.contains("tls12: true"));
241
242 let tls13_only = format!("{:?}", TlsPolicy::hardened().tls13_only());
243 assert!(tls13_only.contains("tls13: true"));
244 assert!(tls13_only.contains("tls12: false"));
245 }
246
247 #[test]
251 fn install_as_process_default_is_idempotent() {
252 TlsPolicy::hardened().install_as_process_default();
253 TlsPolicy::hardened()
254 .tls13_only()
255 .install_as_process_default();
256 }
257
258 #[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
259 #[test]
260 fn server_config_from_pem_builds_a_working_config_from_a_self_signed_cert() {
261 let cert = crate::tls::generate_self_signed_cert(vec!["localhost".to_string()])
262 .expect("generate self-signed cert");
263
264 let config = TlsPolicy::hardened()
265 .server_config_from_pem(cert.cert_pem.as_bytes(), cert.key_pem.as_bytes())
266 .expect("build server config from valid PEM");
267
268 assert_eq!(config.alpn_protocols, crate::server::alpn_protocols(false));
269 }
270
271 #[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
272 #[test]
273 fn server_config_from_pem_rejects_garbage_input() {
274 let err = TlsPolicy::hardened()
275 .server_config_from_pem(b"not a certificate", b"not a key")
276 .expect_err("garbage PEM must not build a config");
277 assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
278 }
279
280 #[cfg(all(feature = "cert-gen", any(feature = "tor", feature = "i2p")))]
281 #[test]
282 fn server_config_from_pem_rejects_a_key_that_does_not_match_the_cert() {
283 let cert_a = crate::tls::generate_self_signed_cert(vec!["a.example".to_string()])
284 .expect("generate cert a");
285 let cert_b = crate::tls::generate_self_signed_cert(vec!["b.example".to_string()])
286 .expect("generate cert b");
287
288 let err = TlsPolicy::hardened()
289 .server_config_from_pem(cert_a.cert_pem.as_bytes(), cert_b.key_pem.as_bytes())
290 .expect_err("mismatched cert/key pair must not build a config");
291 assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
292 }
293}