1use crate::request::normalise_name;
10
11pub const MAX_HELLO: usize = 64 * 1024;
15
16const RECORD_HEADER: usize = 5;
17const MAX_RECORD: usize = 16 * 1024 + 2048;
18const CONTENT_HANDSHAKE: u8 = 22;
19const HANDSHAKE_CLIENT_HELLO: u8 = 1;
20const EXT_SERVER_NAME: u16 = 0;
21const NAME_TYPE_HOST: u8 = 0;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum Hello {
26 Incomplete,
28 ServerName(Option<String>),
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
34#[non_exhaustive]
35pub enum HelloError {
36 #[error("the tunnel does not start with a TLS handshake")]
38 NotTls,
39 #[error("malformed TLS ClientHello")]
41 Malformed,
42 #[error("TLS ClientHello larger than {MAX_HELLO} bytes")]
44 TooLarge,
45}
46
47pub fn server_name(buf: &[u8]) -> Result<Hello, HelloError> {
49 let mut handshake = Vec::new();
50 let mut pos = 0;
51 loop {
52 let Some(&[content, major, _, len_hi, len_lo]) = buf.get(pos..pos + RECORD_HEADER) else {
53 return Ok(Hello::Incomplete);
54 };
55 if content != CONTENT_HANDSHAKE || major != 3 {
56 return Err(HelloError::NotTls);
57 }
58 let len = usize::from(u16::from_be_bytes([len_hi, len_lo]));
59 if len == 0 || len > MAX_RECORD {
60 return Err(HelloError::Malformed);
61 }
62 let start = pos + RECORD_HEADER;
63 let Some(fragment) = buf.get(start..start + len) else {
64 return Ok(Hello::Incomplete);
65 };
66 handshake.extend_from_slice(fragment);
67 pos = start + len;
68
69 if let Some(&[kind, len0, len1, len2]) = handshake.get(..4) {
70 if kind != HANDSHAKE_CLIENT_HELLO {
71 return Err(HelloError::Malformed);
72 }
73 let body_len = usize::from(len0) << 16 | usize::from(len1) << 8 | usize::from(len2);
74 if body_len > MAX_HELLO {
75 return Err(HelloError::TooLarge);
76 }
77 if let Some(body) = handshake.get(4..4 + body_len) {
78 return client_hello_sni(body).map(Hello::ServerName);
79 }
80 }
81 }
82}
83
84fn client_hello_sni(body: &[u8]) -> Result<Option<String>, HelloError> {
85 let mut r = Reader(body);
86 r.take(2 + 32)?; let session_id_len = r.u8()?;
88 r.take(session_id_len.into())?;
89 let suites_len = r.u16()?;
90 r.take(suites_len.into())?;
91 let compression_len = r.u8()?;
92 r.take(compression_len.into())?;
93 if r.0.is_empty() {
94 return Ok(None);
95 }
96 let ext_len = r.u16()?;
97 let mut exts = Reader(r.take(ext_len.into())?);
98 r.end()?;
99 let mut name = None;
100 while !exts.0.is_empty() {
101 let kind = exts.u16()?;
102 let len = exts.u16()?;
103 let data = exts.take(len.into())?;
104 if kind == EXT_SERVER_NAME {
105 if name.is_some() {
106 return Err(HelloError::Malformed);
107 }
108 name = Some(server_name_list(data)?);
109 }
110 }
111 Ok(name.flatten())
112}
113
114fn server_name_list(data: &[u8]) -> Result<Option<String>, HelloError> {
116 let mut r = Reader(data);
117 let list_len = r.u16()?;
118 let mut list = Reader(r.take(list_len.into())?);
119 r.end()?;
120 let mut host = None;
121 while !list.0.is_empty() {
122 let kind = list.u8()?;
123 let len = list.u16()?;
124 let value = list.take(len.into())?;
125 if kind != NAME_TYPE_HOST {
126 continue;
127 }
128 if host.is_some() {
129 return Err(HelloError::Malformed);
130 }
131 let name = std::str::from_utf8(value)
134 .ok()
135 .and_then(normalise_name)
136 .ok_or(HelloError::Malformed)?;
137 host = Some(name);
138 }
139 Ok(host)
140}
141
142struct Reader<'a>(&'a [u8]);
144
145impl<'a> Reader<'a> {
146 fn take(&mut self, n: usize) -> Result<&'a [u8], HelloError> {
147 let (head, rest) = self.0.split_at_checked(n).ok_or(HelloError::Malformed)?;
148 self.0 = rest;
149 Ok(head)
150 }
151
152 fn array<const N: usize>(&mut self) -> Result<[u8; N], HelloError> {
153 let (head, rest) = self.0.split_first_chunk().ok_or(HelloError::Malformed)?;
154 self.0 = rest;
155 Ok(*head)
156 }
157
158 fn u8(&mut self) -> Result<u8, HelloError> {
159 let [b] = self.array()?;
160 Ok(b)
161 }
162
163 fn u16(&mut self) -> Result<u16, HelloError> {
164 self.array().map(u16::from_be_bytes)
165 }
166
167 fn end(&self) -> Result<(), HelloError> {
168 if self.0.is_empty() {
169 Ok(())
170 } else {
171 Err(HelloError::Malformed)
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests;