1#![forbid(unsafe_code)]
3use std::sync::Arc;
6
7use rustls::pki_types::ServerName;
8use tokio::net::TcpStream;
9use tokio_rustls::client::TlsStream as TokioTlsStream;
10use tokio_rustls::TlsConnector;
11
12use super::client_config::{build_client_config, TlsClientOptions};
13use super::TlsConnectOptions;
14use crate::errors::{SshCliError, SshCliResult};
15
16pub type TlsStream = TokioTlsStream<TcpStream>;
18
19pub async fn dial_tls(host: &str, port: u16, opts: &TlsConnectOptions) -> SshCliResult<TlsStream> {
24 let server_name = ServerName::try_from(opts.sni.as_str())
25 .map_err(|e| SshCliError::tls_msg(format!("invalid TLS SNI '{}': {e}", opts.sni)))?
26 .to_owned();
27
28 let client_opts = TlsClientOptions {
29 client_cert: opts.client_cert.clone(),
30 client_key: opts.client_key.clone(),
31 extra_root_pem: None,
32 };
33 let config = build_client_config(&client_opts)?;
34 let connector = TlsConnector::from(config);
35
36 let tcp = crate::net::dial_tcp(host, port).await.map_err(|e| {
37 SshCliError::ConnectionFailed(format!("TCP dial failed for {host}:{port}: {e}"))
38 })?;
39 if let Err(e) = tcp.set_nodelay(true) {
40 tracing::debug!(err = %e, "set_nodelay on TLS socket failed");
41 }
42
43 tracing::info!(
44 host,
45 port,
46 sni = %opts.sni,
47 mtls = opts.client_cert.is_some(),
48 "starting TLS handshake (SSH-over-TLS)"
49 );
50
51 connector
52 .connect(server_name, tcp)
53 .await
54 .map_err(|e| SshCliError::tls_msg(format!("TLS handshake failed for {host}:{port}: {e}")))
55}
56
57#[allow(dead_code)] pub async fn dial_tls_with_config(
60 host: &str,
61 port: u16,
62 sni: &str,
63 config: Arc<rustls::ClientConfig>,
64) -> SshCliResult<TlsStream> {
65 let server_name = ServerName::try_from(sni)
66 .map_err(|e| SshCliError::tls_msg(format!("invalid TLS SNI '{sni}': {e}")))?
67 .to_owned();
68 let connector = TlsConnector::from(config);
69 let tcp = crate::net::dial_tcp(host, port).await.map_err(|e| {
70 SshCliError::ConnectionFailed(format!("TCP dial failed for {host}:{port}: {e}"))
71 })?;
72 let _ = tcp.set_nodelay(true);
73 connector
74 .connect(server_name, tcp)
75 .await
76 .map_err(|e| SshCliError::tls_msg(format!("TLS handshake failed for {host}:{port}: {e}")))
77}