1use std::sync::Arc;
28
29use rustls_pki_types::pem::PemObject as _;
30use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
31use tokio_rustls::rustls::{ClientConfig, RootCertStore, ServerConfig};
32use tokio_rustls::{TlsAcceptor, TlsConnector};
33
34#[derive(Debug, thiserror::Error)]
40#[non_exhaustive]
41pub enum TlsError {
42 #[error("{0} is not a name a certificate can be checked against")]
44 UnusableName(String),
45 #[error("reading {what}: {detail}")]
47 Material {
48 what: String,
50 detail: String,
52 },
53 #[error("tls configuration: {0}")]
55 Config(String),
56 #[error("tls handshake with {peer}: {detail}")]
59 Handshake {
60 peer: String,
62 detail: String,
64 },
65}
66
67#[derive(Clone)]
69pub struct ClientTls {
70 config: Arc<ClientConfig>,
71}
72
73impl std::fmt::Debug for ClientTls {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.write_str("ClientTls { .. }")
77 }
78}
79
80#[derive(Debug, Clone, Default)]
82pub struct TrustAnchors {
83 extra: Vec<CertificateDer<'static>>,
85 system: bool,
87}
88
89impl TrustAnchors {
90 #[must_use]
97 pub fn system() -> Self {
98 Self {
99 extra: Vec::new(),
100 system: true,
101 }
102 }
103
104 #[must_use]
110 pub fn only() -> Self {
111 Self {
112 extra: Vec::new(),
113 system: false,
114 }
115 }
116
117 pub fn add_pem(&mut self, pem: &[u8]) -> Result<(), TlsError> {
119 let certs: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(pem)
120 .collect::<Result<_, _>>()
121 .map_err(|error| TlsError::Material {
122 what: "trust anchor".to_owned(),
123 detail: error.to_string(),
124 })?;
125 if certs.is_empty() {
126 return Err(TlsError::Material {
127 what: "trust anchor".to_owned(),
128 detail: "no certificate found in the PEM data".to_owned(),
129 });
130 }
131 self.extra.extend(certs);
132 Ok(())
133 }
134
135 fn store(&self) -> Result<RootCertStore, TlsError> {
136 let mut store = RootCertStore::empty();
137 if self.system {
138 let loaded = rustls_native_certs::load_native_certs();
139 for error in &loaded.errors {
140 tracing::warn!(%error, "could not read part of the platform trust store");
143 }
144 for cert in loaded.certs {
145 if let Err(error) = store.add(cert) {
148 tracing::debug!(%error, "skipping an unusable root from the platform store");
149 }
150 }
151 }
152 for cert in &self.extra {
153 store
154 .add(cert.clone())
155 .map_err(|error| TlsError::Config(error.to_string()))?;
156 }
157 if store.is_empty() {
158 return Err(TlsError::Config(
159 "no trust anchors: every certificate would be refused".to_owned(),
160 ));
161 }
162 Ok(store)
163 }
164}
165
166impl ClientTls {
167 pub fn new(anchors: &TrustAnchors) -> Result<Self, TlsError> {
169 Self::with_identity(anchors, None)
170 }
171
172 pub fn with_identity(
178 anchors: &TrustAnchors,
179 identity: Option<Identity>,
180 ) -> Result<Self, TlsError> {
181 let roots = anchors.store()?;
182 let builder = ClientConfig::builder().with_root_certificates(roots);
183
184 let config = match identity {
185 Some(identity) => builder
186 .with_client_auth_cert(identity.chain, identity.key)
187 .map_err(|error| TlsError::Config(error.to_string()))?,
188 None => builder.with_no_client_auth(),
189 };
190
191 Ok(Self {
192 config: Arc::new(config),
193 })
194 }
195
196 #[must_use]
198 pub fn connector(&self) -> TlsConnector {
199 TlsConnector::from(Arc::clone(&self.config))
200 }
201
202 #[cfg(feature = "quic")]
204 pub(crate) fn quic_config(&self) -> Result<quinn::ClientConfig, TlsError> {
205 let config = self.quic_rustls_config();
206 let crypto = quinn::crypto::rustls::QuicClientConfig::try_from(config)
207 .map_err(|error| TlsError::Config(error.to_string()))?;
208 Ok(quinn::ClientConfig::new(Arc::new(crypto)))
209 }
210
211 #[cfg(feature = "quic")]
212 fn quic_rustls_config(&self) -> ClientConfig {
213 let mut config = (*self.config).clone();
214 config.alpn_protocols = vec![b"sip/2".to_vec()];
215 config.enable_early_data = false;
216 config
217 }
218}
219
220pub struct Identity {
222 chain: Vec<CertificateDer<'static>>,
223 key: PrivateKeyDer<'static>,
224}
225
226impl std::fmt::Debug for Identity {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 f.write_str("Identity { .. }")
230 }
231}
232
233impl Identity {
234 pub fn from_pem(cert_pem: &[u8], key_pem: &[u8]) -> Result<Self, TlsError> {
236 let chain: Vec<CertificateDer<'static>> = CertificateDer::pem_slice_iter(cert_pem)
237 .collect::<Result<_, _>>()
238 .map_err(|error| TlsError::Material {
239 what: "certificate".to_owned(),
240 detail: error.to_string(),
241 })?;
242 if chain.is_empty() {
243 return Err(TlsError::Material {
244 what: "certificate".to_owned(),
245 detail: "no certificate found in the PEM data".to_owned(),
246 });
247 }
248
249 let key = PrivateKeyDer::from_pem_slice(key_pem).map_err(|error| TlsError::Material {
250 what: "private key".to_owned(),
251 detail: error.to_string(),
252 })?;
253
254 Ok(Self { chain, key })
255 }
256
257 fn validate_server_chain(&self) -> Result<(), TlsError> {
266 let Some((leaf, issuers)) = self.chain.split_first() else {
267 return Err(TlsError::Config(
268 "server certificate chain has no leaf".to_owned(),
269 ));
270 };
271 let end_entity = webpki::EndEntityCert::try_from(leaf).map_err(|error| {
272 TlsError::Config(format!("invalid server certificate leaf: {error}"))
273 })?;
274 let Some((anchor_certificate, intermediates)) = issuers.split_last() else {
275 return Ok(());
276 };
277 let anchor = webpki::anchor_from_trusted_cert(anchor_certificate).map_err(|error| {
278 TlsError::Config(format!("invalid server certificate chain anchor: {error}"))
279 })?;
280 let provider = tokio_rustls::rustls::crypto::ring::default_provider();
281 let anchors = [anchor];
282 let verified = end_entity
283 .verify_for_usage(
284 provider.signature_verification_algorithms.all,
285 &anchors,
286 intermediates,
287 UnixTime::now(),
288 webpki::KeyUsage::server_auth(),
289 None,
290 None,
291 )
292 .map_err(|error| {
293 TlsError::Config(format!("invalid server certificate chain: {error}"))
294 })?;
295 let supplied_in_order = verified
296 .intermediate_certificates()
297 .map(webpki::Cert::der)
298 .eq(intermediates.iter().cloned());
299 if !supplied_in_order {
300 return Err(TlsError::Config(
301 "server certificate chain contains an unrelated or out-of-order certificate"
302 .to_owned(),
303 ));
304 }
305 Ok(())
306 }
307}
308
309#[derive(Clone)]
311pub struct ServerTls {
312 config: Arc<ServerConfig>,
313}
314
315impl std::fmt::Debug for ServerTls {
316 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 f.write_str("ServerTls { .. }")
318 }
319}
320
321impl ServerTls {
322 pub fn new(identity: Identity) -> Result<Self, TlsError> {
324 identity.validate_server_chain()?;
325 let config = ServerConfig::builder()
326 .with_no_client_auth()
327 .with_single_cert(identity.chain, identity.key)
328 .map_err(|error| TlsError::Config(error.to_string()))?;
329 Ok(Self {
330 config: Arc::new(config),
331 })
332 }
333
334 #[must_use]
336 pub fn acceptor(&self) -> TlsAcceptor {
337 TlsAcceptor::from(Arc::clone(&self.config))
338 }
339
340 #[cfg(feature = "quic")]
342 pub(crate) fn quic_config(&self) -> Result<quinn::ServerConfig, TlsError> {
343 let config = self.quic_rustls_config();
344 let crypto = quinn::crypto::rustls::QuicServerConfig::try_from(config)
345 .map_err(|error| TlsError::Config(error.to_string()))?;
346 Ok(quinn::ServerConfig::with_crypto(Arc::new(crypto)))
347 }
348
349 #[cfg(feature = "quic")]
350 fn quic_rustls_config(&self) -> ServerConfig {
351 let mut config = (*self.config).clone();
352 config.alpn_protocols = vec![b"sip/2".to_vec()];
353 config.max_early_data_size = 0;
354 config
355 }
356}
357
358pub fn verification_name(uri_host: &str) -> Result<ServerName<'static>, TlsError> {
365 ServerName::try_from(uri_host.to_owned())
366 .map_err(|_| TlsError::UnusableName(uri_host.to_owned()))
367}
368
369#[cfg(test)]
370#[allow(
371 clippy::unwrap_used,
372 clippy::expect_used,
373 clippy::panic,
374 clippy::indexing_slicing
375)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn a_hostname_is_a_usable_verification_name() {
381 assert!(verification_name("sip.example.com").is_ok());
382 assert!(verification_name("example.com").is_ok());
383 }
384
385 #[test]
389 fn an_address_is_accepted_as_a_name() {
390 assert!(verification_name("192.0.2.1").is_ok());
391 }
392
393 #[test]
394 fn something_that_is_not_a_name_is_refused_by_name() {
395 let error = verification_name("not a hostname!").expect_err("refused");
396 assert!(error.to_string().contains("not a hostname!"), "{error}");
397 }
398
399 #[test]
402 fn a_client_with_no_anchors_is_refused_at_construction() {
403 let error = ClientTls::new(&TrustAnchors::only()).expect_err("refused");
404 assert!(error.to_string().contains("no trust anchors"), "{error}");
405 }
406
407 #[test]
408 fn the_system_anchors_are_enough_to_build_a_client() {
409 assert!(ClientTls::new(&TrustAnchors::system()).is_ok());
410 }
411
412 #[test]
413 fn pem_that_holds_no_certificate_is_refused_by_name() {
414 let mut anchors = TrustAnchors::only();
415 let error = anchors.add_pem(b"not a certificate").expect_err("refused");
416 assert!(error.to_string().contains("no certificate"), "{error}");
417 }
418
419 #[test]
420 fn an_identity_needs_both_halves() {
421 let error = Identity::from_pem(b"", b"").expect_err("refused");
422 assert!(error.to_string().contains("certificate"), "{error}");
423 }
424
425 #[test]
428 fn debug_output_does_not_leak_key_material() {
429 let client = ClientTls::new(&TrustAnchors::system()).expect("builds");
430 let printed = format!("{client:?}");
431 assert_eq!(printed, "ClientTls { .. }");
432
433 let ca = sipx_testkit::certs::Ca::new();
434 let (certificate, key) = ca.issue_for("localhost");
435 let identity =
436 Identity::from_pem(certificate.as_bytes(), key.as_bytes()).expect("identity");
437 assert_eq!(format!("{identity:?}"), "Identity { .. }");
438 }
439
440 #[cfg(feature = "quic")]
441 #[test]
442 fn quic_requires_sip2_and_refuses_early_data_in_both_directions() {
443 let client = ClientTls::new(&TrustAnchors::system()).expect("client");
444 let client = client.quic_rustls_config();
445 assert_eq!(client.alpn_protocols, [b"sip/2".to_vec()]);
446 assert!(!client.enable_early_data);
447
448 let ca = sipx_testkit::certs::Ca::new();
449 let (certificate, key) = ca.issue_for("localhost");
450 let identity =
451 Identity::from_pem(certificate.as_bytes(), key.as_bytes()).expect("identity");
452 let server = ServerTls::new(identity)
453 .expect("server")
454 .quic_rustls_config();
455 assert_eq!(server.alpn_protocols, [b"sip/2".to_vec()]);
456 assert_eq!(server.max_early_data_size, 0);
457 }
458
459 #[cfg(feature = "quic")]
462 #[tokio::test]
463 async fn a_resumed_client_cannot_deliver_early_data_to_a_sipx_server() {
464 let ca = sipx_testkit::certs::Ca::new();
465 let (certificate, key) = ca.issue_for("localhost");
466 let identity =
467 Identity::from_pem(certificate.as_bytes(), key.as_bytes()).expect("identity");
468 let server_policy = ServerTls::new(identity).expect("server policy");
469
470 let mut permissive = server_policy.quic_rustls_config();
474 permissive.max_early_data_size = u32::MAX;
475 let permissive = quinn::crypto::rustls::QuicServerConfig::try_from(permissive)
476 .map(|crypto| quinn::ServerConfig::with_crypto(Arc::new(crypto)))
477 .expect("permissive ticket server");
478 let rejecting = server_policy.quic_config().expect("sipx QUIC server");
479 let server =
480 quinn::Endpoint::server(permissive, "127.0.0.1:0".parse().expect("server address"))
481 .expect("server endpoint");
482 let server_addr = server.local_addr().expect("server address");
483
484 let mut anchors = TrustAnchors::only();
485 anchors
486 .add_pem(ca.pem().as_bytes())
487 .expect("test authority");
488 let mut client_tls = ClientTls::new(&anchors)
489 .expect("client policy")
490 .quic_rustls_config();
491 client_tls.enable_early_data = true;
492 let client_crypto = quinn::crypto::rustls::QuicClientConfig::try_from(client_tls)
493 .expect("early-data client");
494 let mut client_config = quinn::ClientConfig::new(Arc::new(client_crypto));
495 client_config.transport_config(crate::quic::transport_config());
496 let mut client = quinn::Endpoint::client("127.0.0.1:0".parse().expect("client address"))
497 .expect("client endpoint");
498 client.set_default_client_config(client_config);
499
500 let (ready, configured) = tokio::sync::oneshot::channel();
501 let server_task = tokio::spawn(async move {
502 let first = server
503 .accept()
504 .await
505 .expect("first connection")
506 .await
507 .expect("first handshake");
508 let (mut marker, _unused) = first.open_bi().await.expect("1-RTT marker stream");
509 marker.write_all(b"ready").await.expect("1-RTT marker");
510 marker.finish().expect("1-RTT marker finishes");
511
512 server.set_server_config(Some(rejecting));
513 ready.send(()).expect("client waits for configuration");
514 let second = server
515 .accept()
516 .await
517 .expect("resumed connection")
518 .await
519 .expect("resumption handshake");
520 let (_reply, mut request) = second.accept_bi().await.expect("request stream");
521 let was_early = request.is_0rtt();
522 let bytes = request.read_to_end(1024).await.expect("request bytes");
523 (was_early, bytes)
524 });
525
526 let first = client
527 .connect(server_addr, "localhost")
528 .expect("first connect starts")
529 .await
530 .expect("first connect");
531 let (_unused, mut marker) = first.accept_bi().await.expect("server marker");
532 assert_eq!(
533 marker.read_to_end(16).await.expect("marker bytes"),
534 b"ready"
535 );
536 drop(first);
537 configured.await.expect("rejecting server installed");
538
539 let (resumed, accepted) = client
540 .connect(server_addr, "localhost")
541 .expect("resumption starts")
542 .into_0rtt()
543 .expect("client has early-data keys");
544 let (mut request, _reply) = resumed.open_bi().await.expect("early request stream");
545 request
546 .write_all(b"SIP request attempted as early data")
547 .await
548 .expect("early write is queued");
549 request.finish().expect("request finishes");
550 assert!(!accepted.await, "sipx accepted replayable early data");
551 let (mut request, _reply) = resumed.open_bi().await.expect("1-RTT request stream");
552 request
553 .write_all(b"SIP request attempted as early data")
554 .await
555 .expect("1-RTT retry writes");
556 request.finish().expect("1-RTT retry finishes");
557 let (was_early, bytes) = server_task.await.expect("server task");
558 assert!(!was_early, "the server exposed a 0-RTT request stream");
559 assert_eq!(bytes, b"SIP request attempted as early data");
560 }
561}