1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Client-side handshake entry point.
use std::ffi::CString;
use std::net::IpAddr;
use std::os::fd::AsRawFd as _;
use std::sync::Arc;
use crate::error::{last_error, Error, Result};
use crate::ffi::Ssl;
use crate::stream::{attach_socket_bio, drive_handshake, TlsStream};
use crate::ClientConfig;
/// Connector that drives outbound TLS handshakes on top of an
/// already-connected TCP stream.
///
/// Cheap to clone (it's just an `Arc`); one connector can fan out across
/// many tasks.
#[derive(Clone, Debug)]
pub struct TlsConnector {
cfg: Arc<ClientConfig>,
}
impl TlsConnector {
/// Build a connector from a finished [`ClientConfig`]. Accepts
/// either an `Arc<ClientConfig>` (cheap clone of an existing
/// `Arc`) or an owned `ClientConfig` (auto-wrapped in a fresh
/// `Arc`).
pub fn new(cfg: impl Into<Arc<ClientConfig>>) -> Self {
Self { cfg: cfg.into() }
}
/// Drive the TLS handshake to completion against an already-connected
/// TCP stream.
///
/// `server_name` is the peer identity to authenticate. It may be:
///
/// - a DNS name (e.g. `"example.com"`) — sent as the SNI extension
/// and matched against the certificate's DNS SANs / CN; or
/// - an IP-address literal (e.g. `"10.0.0.5"` or `"2001:db8::1"`)
/// — *not* sent as SNI (RFC 6066 §3 forbids IPs in SNI) and
/// matched against the certificate's iPAddress SANs.
///
/// Both checks happen inside libssl as part of the handshake; a
/// mismatch fails the handshake with a verification error. Disabling
/// verification on the underlying [`ClientConfig`] disables this
/// check too.
///
/// # Errors
///
/// - `Error::Init` if `server_name` is empty, contains a NUL byte,
/// or libssl rejects it.
/// - `Error::Handshake` on any TLS handshake failure.
pub async fn connect(
&self,
server_name: &str,
tcp: tokio::net::TcpStream,
) -> Result<TlsStream> {
if server_name.is_empty() {
return Err(Error::Init("server_name must not be empty".into()));
}
let mut ssl = new_ssl(&self.cfg)?;
let name_c = CString::new(server_name)
.map_err(|_| Error::Init("server_name contains an embedded NUL byte".into()))?;
// RFC 6066 §3: the SNI extension carries DNS names only, never
// IP literals. Route IP-literal peers through the iPAddress SAN
// path; everything else gets SNI + DNS-SAN/CN matching.
match server_name.parse::<IpAddr>() {
Ok(_) => {
// SAFETY: ssl is fresh; SSL_get0_param returns a
// borrowed X509_VERIFY_PARAM owned by the SSL handle.
// X509_VERIFY_PARAM_set1_ip_asc accepts the same
// textual forms as `IpAddr::FromStr` (dotted-quad and
// RFC 5952 IPv6); returns 1 on success.
let param = unsafe { aws_lc_sys::SSL_get0_param(ssl.as_ptr()) };
if param.is_null() {
return Err(Error::Init(
"SSL_get0_param returned null for fresh SSL".into(),
));
}
let ok =
unsafe { aws_lc_sys::X509_VERIFY_PARAM_set1_ip_asc(param, name_c.as_ptr()) };
if ok != 1 {
return Err(Error::Init(format!(
"X509_VERIFY_PARAM_set1_ip_asc({server_name}): {}",
last_error()
)));
}
}
Err(_) => {
// SAFETY: ssl is fresh; the CString lives for the call.
// Both setters either succeed or queue an error.
unsafe {
if aws_lc_sys::SSL_set_tlsext_host_name(ssl.as_ptr(), name_c.as_ptr()) != 1 {
return Err(Error::Init(format!(
"SSL_set_tlsext_host_name({server_name}): {}",
last_error()
)));
}
if aws_lc_sys::SSL_set1_host(ssl.as_ptr(), name_c.as_ptr()) != 1 {
return Err(Error::Init(format!(
"SSL_set1_host({server_name}): {}",
last_error()
)));
}
}
}
}
// SAFETY: ssl is fresh; `tcp` owns the non-blocking socket fd
// for the rest of this function and the resulting TlsStream.
unsafe {
attach_socket_bio(&mut ssl, tcp.as_raw_fd())?;
aws_lc_sys::SSL_set_connect_state(ssl.as_ptr());
}
// SAFETY: ssl is wired to tcp's fd and set to connect state.
unsafe {
drive_handshake(&mut ssl, &tcp).await?;
}
let mut stream = TlsStream::from_parts(ssl, tcp, self.cfg.ktls_disabled);
stream.try_auto_install_ktls()?;
Ok(stream)
}
}
fn new_ssl(cfg: &ClientConfig) -> Result<Ssl> {
// SAFETY: cfg.ctx_ptr() is a live SSL_CTX owned by `cfg`; SSL_new
// returns either an owned SSL or null on alloc failure.
let raw = unsafe { aws_lc_sys::SSL_new(cfg.ctx_ptr()) };
// SAFETY: `raw` is the freshly-owned SSL (or null).
unsafe { Ssl::from_raw(raw) }.ok_or_else(|| Error::Init(format!("SSL_new: {}", last_error())))
}