Skip to main content

net_wire/tls/
builder.rs

1//! Caller-buffer TLS extension and hello construction.
2
3use super::{
4    error::TlsBuildError,
5    extensions::extension::{TlsExtension, TlsExtensions},
6    hello::{ClientHello, ServerHello},
7    types::{TlsCipherSuite, TlsCompressionMethod, TlsExtensionType, TlsProtocolVersion},
8};
9
10/// Builds one TLS extension TLV in caller-owned storage.
11pub struct TlsExtensionBuilder<'a, 'b> {
12    buffer: &'a mut [u8],
13    extension_type: TlsExtensionType,
14    data: &'b [u8],
15}
16
17impl<'a, 'b> TlsExtensionBuilder<'a, 'b> {
18    /// Creates a builder that copies the supplied extension payload.
19    pub fn new(buffer: &'a mut [u8], extension_type: TlsExtensionType, data: &'b [u8]) -> Self {
20        Self {
21            buffer,
22            extension_type,
23            data,
24        }
25    }
26
27    /// Validates all inputs before writing the extension.
28    pub fn build(self) -> Result<TlsExtension<'a>, TlsBuildError> {
29        let data_length =
30            u16::try_from(self.data.len()).map_err(|_| TlsBuildError::LengthTooLarge)?;
31        let required = 4usize
32            .checked_add(self.data.len())
33            .ok_or(TlsBuildError::LengthTooLarge)?;
34        ensure_capacity(self.buffer.len(), required)?;
35
36        let bytes = &mut self.buffer[..required];
37        bytes[..2].copy_from_slice(&self.extension_type.raw().to_be_bytes());
38        bytes[2..4].copy_from_slice(&data_length.to_be_bytes());
39        bytes[4..].copy_from_slice(self.data);
40
41        Ok(TlsExtensions::new(bytes)
42            .next()
43            .expect("built extension is present")
44            .expect("validated extension encoding"))
45    }
46}
47
48/// Builds a ClientHello body in caller-owned storage.
49pub struct ClientHelloBuilder<'a, 'b> {
50    buffer: &'a mut [u8],
51    legacy_version: TlsProtocolVersion,
52    random: &'b [u8; 32],
53    session_id: &'b [u8],
54    cipher_suites: &'b [u8],
55    compression_methods: &'b [u8],
56    extensions: Option<&'b [u8]>,
57}
58
59impl<'a, 'b> ClientHelloBuilder<'a, 'b> {
60    /// Creates a builder from ordered, already encoded variable-length fields.
61    pub fn new(
62        buffer: &'a mut [u8],
63        legacy_version: TlsProtocolVersion,
64        random: &'b [u8; 32],
65        session_id: &'b [u8],
66        cipher_suites: &'b [u8],
67        compression_methods: &'b [u8],
68        extensions: Option<&'b [u8]>,
69    ) -> Self {
70        Self {
71            buffer,
72            legacy_version,
73            random,
74            session_id,
75            cipher_suites,
76            compression_methods,
77            extensions,
78        }
79    }
80
81    /// Validates all fields and capacity before writing the ClientHello body.
82    pub fn build(self) -> Result<ClientHello<'a>, TlsBuildError> {
83        let session_length = validate_u8_vector(self.session_id, 0, 32)?;
84        let cipher_length = validate_u16_vector(self.cipher_suites, 2, true)?;
85        let compression_length = validate_u8_vector(self.compression_methods, 1, u8::MAX as usize)?;
86        let extension_length = validate_extensions(self.extensions)?;
87
88        let required = 34usize
89            .checked_add(1 + self.session_id.len())
90            .and_then(|value| value.checked_add(2 + self.cipher_suites.len()))
91            .and_then(|value| value.checked_add(1 + self.compression_methods.len()))
92            .and_then(|value| {
93                value.checked_add(self.extensions.map_or(0, |extensions| 2 + extensions.len()))
94            })
95            .ok_or(TlsBuildError::LengthTooLarge)?;
96        ensure_capacity(self.buffer.len(), required)?;
97
98        let bytes = &mut self.buffer[..required];
99        bytes[..2].copy_from_slice(&self.legacy_version.raw().to_be_bytes());
100        bytes[2..34].copy_from_slice(self.random);
101        let mut offset = 34;
102        bytes[offset] = session_length;
103        offset += 1;
104        bytes[offset..offset + self.session_id.len()].copy_from_slice(self.session_id);
105        offset += self.session_id.len();
106        bytes[offset..offset + 2].copy_from_slice(&cipher_length.to_be_bytes());
107        offset += 2;
108        bytes[offset..offset + self.cipher_suites.len()].copy_from_slice(self.cipher_suites);
109        offset += self.cipher_suites.len();
110        bytes[offset] = compression_length;
111        offset += 1;
112        bytes[offset..offset + self.compression_methods.len()]
113            .copy_from_slice(self.compression_methods);
114        offset += self.compression_methods.len();
115        if let (Some(extensions), Some(length)) = (self.extensions, extension_length) {
116            bytes[offset..offset + 2].copy_from_slice(&length.to_be_bytes());
117            offset += 2;
118            bytes[offset..offset + extensions.len()].copy_from_slice(extensions);
119        }
120
121        Ok(ClientHello::parse(bytes).expect("validated ClientHello builder fields"))
122    }
123}
124
125/// Builds a ServerHello body in caller-owned storage.
126pub struct ServerHelloBuilder<'a, 'b> {
127    buffer: &'a mut [u8],
128    legacy_version: TlsProtocolVersion,
129    random: &'b [u8; 32],
130    session_id: &'b [u8],
131    cipher_suite: TlsCipherSuite,
132    compression_method: TlsCompressionMethod,
133    extensions: Option<&'b [u8]>,
134}
135
136impl<'a, 'b> ServerHelloBuilder<'a, 'b> {
137    /// Creates a builder from fixed fields and ordered encoded extension TLVs.
138    pub fn new(
139        buffer: &'a mut [u8],
140        legacy_version: TlsProtocolVersion,
141        random: &'b [u8; 32],
142        session_id: &'b [u8],
143        cipher_suite: TlsCipherSuite,
144        compression_method: TlsCompressionMethod,
145        extensions: Option<&'b [u8]>,
146    ) -> Self {
147        Self {
148            buffer,
149            legacy_version,
150            random,
151            session_id,
152            cipher_suite,
153            compression_method,
154            extensions,
155        }
156    }
157
158    /// Validates all fields and capacity before writing the ServerHello body.
159    pub fn build(self) -> Result<ServerHello<'a>, TlsBuildError> {
160        let session_length = validate_u8_vector(self.session_id, 0, 32)?;
161        let extension_length = validate_extensions(self.extensions)?;
162        let required = 38usize
163            .checked_add(self.session_id.len())
164            .and_then(|value| {
165                value.checked_add(self.extensions.map_or(0, |extensions| 2 + extensions.len()))
166            })
167            .ok_or(TlsBuildError::LengthTooLarge)?;
168        ensure_capacity(self.buffer.len(), required)?;
169
170        let bytes = &mut self.buffer[..required];
171        bytes[..2].copy_from_slice(&self.legacy_version.raw().to_be_bytes());
172        bytes[2..34].copy_from_slice(self.random);
173        let mut offset = 34;
174        bytes[offset] = session_length;
175        offset += 1;
176        bytes[offset..offset + self.session_id.len()].copy_from_slice(self.session_id);
177        offset += self.session_id.len();
178        bytes[offset..offset + 2].copy_from_slice(&self.cipher_suite.raw().to_be_bytes());
179        offset += 2;
180        bytes[offset] = self.compression_method.raw();
181        offset += 1;
182        if let (Some(extensions), Some(length)) = (self.extensions, extension_length) {
183            bytes[offset..offset + 2].copy_from_slice(&length.to_be_bytes());
184            offset += 2;
185            bytes[offset..offset + extensions.len()].copy_from_slice(extensions);
186        }
187
188        Ok(ServerHello::parse(bytes).expect("validated ServerHello builder fields"))
189    }
190}
191
192fn validate_u8_vector(bytes: &[u8], minimum: usize, maximum: usize) -> Result<u8, TlsBuildError> {
193    if bytes.len() > u8::MAX as usize {
194        return Err(TlsBuildError::LengthTooLarge);
195    }
196    if bytes.len() < minimum || bytes.len() > maximum {
197        return Err(TlsBuildError::InvalidValue);
198    }
199    Ok(bytes.len() as u8)
200}
201
202fn validate_u16_vector(bytes: &[u8], minimum: usize, even: bool) -> Result<u16, TlsBuildError> {
203    if bytes.len() < minimum || (even && !bytes.len().is_multiple_of(2)) {
204        return Err(TlsBuildError::InvalidValue);
205    }
206    u16::try_from(bytes.len()).map_err(|_| TlsBuildError::LengthTooLarge)
207}
208
209fn validate_extensions(extensions: Option<&[u8]>) -> Result<Option<u16>, TlsBuildError> {
210    let Some(extensions) = extensions else {
211        return Ok(None);
212    };
213    let length = u16::try_from(extensions.len()).map_err(|_| TlsBuildError::LengthTooLarge)?;
214    for extension in TlsExtensions::new(extensions) {
215        extension.map_err(|_| TlsBuildError::InvalidValue)?;
216    }
217    Ok(Some(length))
218}
219
220fn ensure_capacity(available: usize, required: usize) -> Result<(), TlsBuildError> {
221    if available < required {
222        Err(TlsBuildError::BufferTooShort {
223            required,
224            available,
225        })
226    } else {
227        Ok(())
228    }
229}