1#![deny(missing_docs, missing_debug_implementations, unsafe_code)]
2#![warn(unreachable_pub, unused_qualifications, unused_lifetimes)]
3#![warn(
4 clippy::must_use_candidate,
5 clippy::unwrap_in_result,
6 clippy::panic_in_result_fn
7)]
8
9#![cfg_attr(feature = "platform-verifier", doc = "```rust, no_run")]
50#![cfg_attr(not(feature = "platform-verifier"), doc = "```rust, ignore")]
51pub use rustls;
70#[cfg(feature = "native-certs")]
71pub use rustls_native_certs;
73pub use rustls_pki_types;
75#[cfg(feature = "platform-verifier")]
76pub use rustls_platform_verifier;
78pub use webpki;
80#[cfg(feature = "webpki-root-certs")]
81pub use webpki_root_certs;
83
84#[cfg(feature = "futures")]
85use futures_io::{AsyncRead, AsyncWrite};
86use rustls::{
87 ClientConfig, ClientConnection, ConfigBuilder, RootCertStore, StreamOwned,
88 client::WantsClientCert,
89};
90use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName};
91
92use std::{
93 error::Error,
94 fmt,
95 io::{self, Read, Write},
96 sync::Arc,
97};
98
99pub type TlsStream<S> = StreamOwned<ClientConnection, S>;
101
102#[cfg(feature = "futures")]
103pub type AsyncTlsStream<S> = futures_rustls::client::TlsStream<S>;
105
106#[derive(Clone, Default, Debug)]
108pub struct RustlsConnectorConfig {
109 store: Vec<CertificateDer<'static>>,
110 #[cfg(feature = "platform-verifier")]
111 platform_verifier: bool,
112}
113
114impl RustlsConnectorConfig {
115 #[cfg(feature = "webpki-root-certs")]
116 #[must_use]
118 pub fn new_with_webpki_root_certs() -> Self {
119 Self::default().with_webpki_root_certs()
120 }
121
122 #[cfg(feature = "platform-verifier")]
123 #[must_use]
125 pub fn new_with_platform_verifier() -> Self {
126 Self::default().with_platform_verifier()
127 }
128
129 #[cfg(feature = "native-certs")]
130 pub fn new_with_native_certs() -> io::Result<Self> {
136 Self::default().with_native_certs()
137 }
138
139 pub fn add_parsable_certificates(&mut self, mut der_certs: Vec<CertificateDer<'static>>) {
149 self.store.append(&mut der_certs)
150 }
151
152 #[must_use]
157 pub fn with_parsable_certificates(mut self, der_certs: Vec<CertificateDer<'static>>) -> Self {
158 self.add_parsable_certificates(der_certs);
159 self
160 }
161
162 #[cfg(feature = "webpki-root-certs")]
163 #[must_use]
165 pub fn with_webpki_root_certs(mut self) -> Self {
166 self.add_parsable_certificates(webpki_root_certs::TLS_SERVER_ROOT_CERTS.to_vec());
167 self
168 }
169
170 #[cfg(feature = "platform-verifier")]
171 #[must_use]
173 pub fn with_platform_verifier(mut self) -> Self {
174 self.platform_verifier = true;
175 self
176 }
177
178 #[cfg(feature = "native-certs")]
179 pub fn with_native_certs(mut self) -> io::Result<Self> {
185 let certs_result = rustls_native_certs::load_native_certs();
186 for err in certs_result.errors {
187 log::warn!("Got error while loading some native certificates: {err:?}");
188 }
189 if certs_result.certs.is_empty() {
190 return Err(io::Error::other(
191 "Could not load any valid native certificates",
192 ));
193 }
194 self.add_parsable_certificates(certs_result.certs);
195 Ok(self)
196 }
197
198 fn builder(self) -> io::Result<ConfigBuilder<ClientConfig, WantsClientCert>> {
199 let builder = ClientConfig::builder();
200 #[cfg(feature = "platform-verifier")]
201 {
202 if self.platform_verifier {
203 let provider = builder.crypto_provider().clone();
204 #[cfg(target_os = "android")]
208 let verifier = {
209 if !self.store.is_empty() {
210 return Err(io::Error::other(
211 "extra root certificates cannot be combined with the platform verifier on Android",
212 ));
213 }
214 rustls_platform_verifier::Verifier::new(provider)
215 };
216 #[cfg(not(target_os = "android"))]
217 let verifier =
218 rustls_platform_verifier::Verifier::new_with_extra_roots(self.store, provider);
219 let verifier =
220 verifier.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
221 return Ok(builder
224 .dangerous()
225 .with_custom_certificate_verifier(Arc::new(verifier)));
226 }
227 }
228 let mut store = RootCertStore::empty();
229 let (_, ignored) = store.add_parsable_certificates(self.store);
230 if ignored > 0 {
231 log::warn!("{ignored} CA root certificates were ignored due to errors");
232 }
233 if store.is_empty() {
234 return Err(io::Error::other("Could not load any valid certificates"));
235 }
236 Ok(builder.with_root_certificates(store))
237 }
238
239 pub fn connector_with_no_client_auth(self) -> io::Result<RustlsConnector> {
251 Ok(self.builder()?.with_no_client_auth().into())
252 }
253
254 pub fn connector_with_single_cert(
269 self,
270 cert_chain: Vec<CertificateDer<'static>>,
271 key_der: PrivateKeyDer<'static>,
272 ) -> io::Result<RustlsConnector> {
273 Ok(self
274 .builder()?
275 .with_client_auth_cert(cert_chain, key_der)
276 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?
277 .into())
278 }
279}
280
281#[derive(Clone, Debug)]
288pub struct RustlsConnector(Arc<ClientConfig>);
289
290impl From<ClientConfig> for RustlsConnector {
291 fn from(config: ClientConfig) -> Self {
292 Arc::new(config).into()
293 }
294}
295
296impl From<Arc<ClientConfig>> for RustlsConnector {
297 fn from(config: Arc<ClientConfig>) -> Self {
298 Self(config)
299 }
300}
301
302impl RustlsConnector {
303 #[cfg(feature = "webpki-root-certs")]
304 pub fn new_with_webpki_root_certs() -> io::Result<Self> {
314 RustlsConnectorConfig::new_with_webpki_root_certs().connector_with_no_client_auth()
315 }
316
317 #[cfg(feature = "platform-verifier")]
318 pub fn new_with_platform_verifier() -> io::Result<Self> {
328 RustlsConnectorConfig::new_with_platform_verifier().connector_with_no_client_auth()
329 }
330
331 #[cfg(feature = "native-certs")]
332 pub fn new_with_native_certs() -> io::Result<Self> {
342 RustlsConnectorConfig::new_with_native_certs()?.connector_with_no_client_auth()
343 }
344
345 #[allow(clippy::result_large_err)]
352 pub fn connect<S: Read + Write + Send + 'static>(
353 &self,
354 domain: &str,
355 stream: S,
356 ) -> Result<TlsStream<S>, HandshakeError<S>> {
357 let session = ClientConnection::new(
358 self.0.clone(),
359 server_name(domain).map_err(HandshakeError::Failure)?,
360 )
361 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
362 MidHandshakeTlsStream { session, stream }.handshake()
363 }
364
365 #[cfg(feature = "futures")]
366 pub async fn connect_async<S: AsyncRead + AsyncWrite + Send + Unpin + 'static>(
372 &self,
373 domain: &str,
374 stream: S,
375 ) -> io::Result<AsyncTlsStream<S>> {
376 futures_rustls::TlsConnector::from(self.0.clone())
377 .connect(server_name(domain)?, stream)
378 .await
379 }
380}
381
382fn server_name(domain: &str) -> io::Result<ServerName<'static>> {
383 Ok(ServerName::try_from(domain)
384 .map_err(|err| {
385 io::Error::new(
386 io::ErrorKind::InvalidData,
387 format!("Invalid domain name: {err:?}"),
388 )
389 })?
390 .to_owned())
391}
392
393#[derive(Debug)]
395pub struct MidHandshakeTlsStream<S: Read + Write> {
396 session: ClientConnection,
397 stream: S,
398}
399
400impl<S: Read + Write> MidHandshakeTlsStream<S> {
401 pub fn get_ref(&self) -> &S {
403 &self.stream
404 }
405
406 pub fn get_mut(&mut self) -> &mut S {
408 &mut self.stream
409 }
410}
411
412impl<S: Read + Write + Send + 'static> MidHandshakeTlsStream<S> {
413 #[allow(clippy::result_large_err)]
420 pub fn handshake(mut self) -> Result<TlsStream<S>, HandshakeError<S>> {
421 if let Err(e) = self.session.complete_io(&mut self.stream) {
422 if e.kind() == io::ErrorKind::WouldBlock {
423 if self.session.is_handshaking() {
424 return Err(HandshakeError::WouldBlock(self));
425 }
426 } else {
427 return Err(e.into());
428 }
429 }
430 Ok(TlsStream::new(self.session, self.stream))
431 }
432}
433
434impl<S: Read + Write> fmt::Display for MidHandshakeTlsStream<S> {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 f.write_str("MidHandshakeTlsStream")
437 }
438}
439
440#[allow(clippy::large_enum_variant)]
442pub enum HandshakeError<S: Read + Write + Send + 'static> {
443 WouldBlock(MidHandshakeTlsStream<S>),
446 Failure(io::Error),
448}
449
450impl<S: Read + Write + Send + 'static> fmt::Display for HandshakeError<S> {
451 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
452 match self {
453 HandshakeError::WouldBlock(_) => f.write_str("WouldBlock hit during handshake"),
454 HandshakeError::Failure(err) => f.write_fmt(format_args!("IO error: {err}")),
455 }
456 }
457}
458
459impl<S: Read + Write + Send + 'static> fmt::Debug for HandshakeError<S> {
460 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461 let mut d = f.debug_tuple("HandshakeError");
462 match self {
463 HandshakeError::WouldBlock(_) => d.field(&"WouldBlock"),
464 HandshakeError::Failure(err) => d.field(&err),
465 }
466 .finish()
467 }
468}
469
470impl<S: Read + Write + Send + 'static> Error for HandshakeError<S> {
471 fn source(&self) -> Option<&(dyn Error + 'static)> {
472 match self {
473 HandshakeError::Failure(err) => Some(err),
474 _ => None,
475 }
476 }
477}
478
479impl<S: Read + Send + Write + 'static> From<io::Error> for HandshakeError<S> {
480 fn from(err: io::Error) -> Self {
481 HandshakeError::Failure(err)
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn empty_config_fails() {
491 assert!(
492 RustlsConnectorConfig::default()
493 .connector_with_no_client_auth()
494 .is_err()
495 );
496 }
497
498 #[test]
499 #[cfg(feature = "webpki-root-certs")]
500 fn webpki_root_certs_connector_builds() {
501 RustlsConnector::new_with_webpki_root_certs().unwrap();
502 }
503
504 #[test]
505 #[cfg(feature = "platform-verifier")]
506 fn platform_verifier_connector_builds() {
507 RustlsConnector::new_with_platform_verifier().unwrap();
508 }
509
510 #[test]
511 #[cfg(feature = "webpki-root-certs")]
512 fn invalid_certificates_are_skipped() {
513 let mut certs = vec![CertificateDer::from(vec![0x00, 0x01, 0x02])];
514 certs.extend(webpki_root_certs::TLS_SERVER_ROOT_CERTS.iter().cloned());
515 RustlsConnectorConfig::default()
516 .with_parsable_certificates(certs)
517 .connector_with_no_client_auth()
518 .unwrap();
519 }
520
521 #[test]
522 #[cfg(feature = "platform-verifier")]
523 fn platform_verifier_rejects_invalid_extra_roots() {
524 assert!(
525 RustlsConnectorConfig::new_with_platform_verifier()
526 .with_parsable_certificates(vec![CertificateDer::from(vec![0x00, 0x01, 0x02])])
527 .connector_with_no_client_auth()
528 .is_err()
529 );
530 }
531
532 #[test]
533 fn handshake_error_failure_display() {
534 let err: HandshakeError<std::net::TcpStream> =
535 HandshakeError::Failure(io::Error::other("test error"));
536 assert!(err.to_string().contains("test error"));
537 assert!(format!("{err:?}").contains("test error"));
538 assert!(err.source().is_some());
539 }
540}