Skip to main content

net_wire/tls/
hello.rs

1//! ClientHello and ServerHello wire views.
2
3use super::{
4    error::TlsParseError,
5    extensions::extension::TlsExtensions,
6    types::{TlsCipherSuite, TlsCompressionMethod, TlsExtensionType, TlsProtocolVersion},
7};
8
9/// TLS HelloRetryRequest random value.
10pub const HELLO_RETRY_REQUEST_RANDOM: [u8; 32] = [
11    0xcf, 0x21, 0xad, 0x74, 0xe5, 0x9a, 0x61, 0x11, 0xbe, 0x1d, 0x8c, 0x02, 0x1e, 0x65, 0xb8, 0x91,
12    0xc2, 0xa2, 0x11, 0x16, 0x7a, 0xbb, 0x8c, 0x5e, 0x07, 0x9e, 0x09, 0xe2, 0xc8, 0xa8, 0x33, 0x9c,
13];
14
15/// A structurally validated ClientHello body.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct ClientHello<'a> {
18    bytes: &'a [u8],
19    session_id: &'a [u8],
20    cipher_suites: &'a [u8],
21    compression_methods: &'a [u8],
22    extensions: &'a [u8],
23}
24
25impl<'a> ClientHello<'a> {
26    /// Parses an exact ClientHello body.
27    pub fn parse(bytes: &'a [u8]) -> Result<Self, TlsParseError> {
28        need(bytes, 34)?;
29        let mut offset = 34;
30
31        let session_id = take_u8_vector(bytes, &mut offset)?;
32        if session_id.len() > 32 {
33            return Err(TlsParseError::InvalidValue);
34        }
35
36        let cipher_suites = take_u16_vector(bytes, &mut offset)?;
37        if cipher_suites.is_empty() || !cipher_suites.len().is_multiple_of(2) {
38            return Err(TlsParseError::InvalidValue);
39        }
40
41        let compression_methods = take_u8_vector(bytes, &mut offset)?;
42        if compression_methods.is_empty() {
43            return Err(TlsParseError::InvalidValue);
44        }
45
46        let extensions = if offset == bytes.len() {
47            &[]
48        } else {
49            take_u16_vector(bytes, &mut offset)?
50        };
51        if offset != bytes.len() {
52            return Err(TlsParseError::TrailingBytes);
53        }
54
55        Ok(Self {
56            bytes,
57            session_id,
58            cipher_suites,
59            compression_methods,
60            extensions,
61        })
62    }
63
64    /// Returns the legacy ClientHello version.
65    pub fn legacy_version(&self) -> TlsProtocolVersion {
66        TlsProtocolVersion::new(u16::from_be_bytes([self.bytes[0], self.bytes[1]]))
67    }
68
69    /// Returns the 32-byte client random.
70    pub fn random(&self) -> &'a [u8; 32] {
71        self.bytes[2..34]
72            .try_into()
73            .expect("validated ClientHello random")
74    }
75
76    /// Returns the legacy session identifier.
77    pub fn session_id(&self) -> &'a [u8] {
78        self.session_id
79    }
80
81    /// Returns ordered offered cipher suites.
82    pub fn cipher_suites(&self) -> impl Iterator<Item = TlsCipherSuite> + 'a {
83        self.cipher_suites
84            .chunks_exact(2)
85            .map(|bytes| TlsCipherSuite::new(u16::from_be_bytes([bytes[0], bytes[1]])))
86    }
87
88    /// Returns ordered legacy compression methods.
89    pub fn compression_methods(&self) -> impl Iterator<Item = TlsCompressionMethod> + 'a {
90        self.compression_methods
91            .iter()
92            .copied()
93            .map(TlsCompressionMethod::new)
94    }
95
96    /// Returns ordered extension TLVs.
97    pub fn extensions(&self) -> TlsExtensions<'a> {
98        TlsExtensions::new(self.extensions)
99    }
100
101    /// Returns whether a valid supported_versions extension includes TLS 1.3.
102    pub fn offers_tls13(&self) -> bool {
103        self.extensions()
104            .filter_map(Result::ok)
105            .filter(|extension| extension.extension_type() == TlsExtensionType::SUPPORTED_VERSIONS)
106            .filter_map(|extension| extension.client_supported_versions().ok())
107            .any(|versions| {
108                versions
109                    .iter()
110                    .any(|version| version == TlsProtocolVersion::TLS13)
111            })
112    }
113
114    /// Returns the complete represented ClientHello body.
115    pub fn as_bytes(&self) -> &'a [u8] {
116        self.bytes
117    }
118}
119
120/// A structurally validated ServerHello body.
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122pub struct ServerHello<'a> {
123    bytes: &'a [u8],
124    session_id: &'a [u8],
125    cipher_suite: TlsCipherSuite,
126    compression_method: TlsCompressionMethod,
127    extensions: &'a [u8],
128}
129
130impl<'a> ServerHello<'a> {
131    /// Parses an exact ServerHello body.
132    pub fn parse(bytes: &'a [u8]) -> Result<Self, TlsParseError> {
133        need(bytes, 38)?;
134        let mut offset = 34;
135
136        let session_id = take_u8_vector(bytes, &mut offset)?;
137        if session_id.len() > 32 {
138            return Err(TlsParseError::InvalidValue);
139        }
140
141        need(&bytes[offset..], 3)?;
142        let cipher_suite =
143            TlsCipherSuite::new(u16::from_be_bytes([bytes[offset], bytes[offset + 1]]));
144        let compression_method = TlsCompressionMethod::new(bytes[offset + 2]);
145        offset += 3;
146
147        let extensions = if offset == bytes.len() {
148            &[]
149        } else {
150            take_u16_vector(bytes, &mut offset)?
151        };
152        if offset != bytes.len() {
153            return Err(TlsParseError::TrailingBytes);
154        }
155
156        Ok(Self {
157            bytes,
158            session_id,
159            cipher_suite,
160            compression_method,
161            extensions,
162        })
163    }
164
165    /// Returns the legacy ServerHello version.
166    pub fn legacy_version(&self) -> TlsProtocolVersion {
167        TlsProtocolVersion::new(u16::from_be_bytes([self.bytes[0], self.bytes[1]]))
168    }
169
170    /// Returns the 32-byte server random.
171    pub fn random(&self) -> &'a [u8; 32] {
172        self.bytes[2..34]
173            .try_into()
174            .expect("validated ServerHello random")
175    }
176
177    /// Returns the echoed legacy session identifier.
178    pub fn session_id(&self) -> &'a [u8] {
179        self.session_id
180    }
181
182    /// Returns the selected cipher suite as its raw-preserving value.
183    pub fn cipher_suite(&self) -> TlsCipherSuite {
184        self.cipher_suite
185    }
186
187    /// Returns the selected legacy compression method.
188    pub fn compression_method(&self) -> TlsCompressionMethod {
189        self.compression_method
190    }
191
192    /// Returns whether this ServerHello is a HelloRetryRequest.
193    pub fn is_hello_retry_request(&self) -> bool {
194        self.random() == &HELLO_RETRY_REQUEST_RANDOM
195    }
196
197    /// Returns ordered extension TLVs.
198    pub fn extensions(&self) -> TlsExtensions<'a> {
199        TlsExtensions::new(self.extensions)
200    }
201
202    /// Returns the selected supported version when a valid server extension is present.
203    pub fn selected_version(&self) -> Result<Option<TlsProtocolVersion>, TlsParseError> {
204        for extension in self.extensions() {
205            let extension = extension?;
206            if extension.extension_type() == TlsExtensionType::SUPPORTED_VERSIONS {
207                return extension.server_supported_version().map(Some);
208            }
209        }
210        Ok(None)
211    }
212
213    /// Returns the complete represented ServerHello body.
214    pub fn as_bytes(&self) -> &'a [u8] {
215        self.bytes
216    }
217}
218
219fn need(bytes: &[u8], required: usize) -> Result<(), TlsParseError> {
220    if bytes.len() < required {
221        Err(TlsParseError::Incomplete {
222            required,
223            available: bytes.len(),
224        })
225    } else {
226        Ok(())
227    }
228}
229
230fn take_u8_vector<'a>(bytes: &'a [u8], offset: &mut usize) -> Result<&'a [u8], TlsParseError> {
231    need(&bytes[*offset..], 1)?;
232    let length = usize::from(bytes[*offset]);
233    *offset += 1;
234    take(bytes, offset, length)
235}
236
237fn take_u16_vector<'a>(bytes: &'a [u8], offset: &mut usize) -> Result<&'a [u8], TlsParseError> {
238    need(&bytes[*offset..], 2)?;
239    let length = usize::from(u16::from_be_bytes([bytes[*offset], bytes[*offset + 1]]));
240    *offset += 2;
241    take(bytes, offset, length)
242}
243
244fn take<'a>(bytes: &'a [u8], offset: &mut usize, length: usize) -> Result<&'a [u8], TlsParseError> {
245    let end = offset
246        .checked_add(length)
247        .ok_or(TlsParseError::InvalidValue)?;
248    need(&bytes[*offset..], length)?;
249    let result = &bytes[*offset..end];
250    *offset = end;
251    Ok(result)
252}