Botan 3.12.0
Crypto and TLS for C&
Botan::TLS::Record_Layer Class Reference

#include <tls_record_layer_13.h>

Public Types

template<typename ResT>
using ReadResult = std::variant<BytesNeeded, ResT>

Public Member Functions

void clear_read_buffer ()
void copy_data (std::span< const uint8_t > data_from_peer)
void disable_receiving_compat_mode ()
void disable_sending_compat_mode ()
ReadResult< Recordnext_record (Cipher_State *cipher_state=nullptr)
std::vector< uint8_t > prepare_records (Record_Type type, std::span< const uint8_t > data, Cipher_State *cipher_state=nullptr) const
 Record_Layer (Connection_Side side)
void set_record_size_limits (uint16_t outgoing_limit, uint16_t incoming_limit)

Detailed Description

Implementation of the TLS 1.3 record protocol layer

This component transforms bytes received from the peer into bytes containing plaintext TLS messages and vice versa.

Definition at line 46 of file tls_record_layer_13.h.

Member Typedef Documentation

◆ ReadResult

template<typename ResT>
using Botan::TLS::Record_Layer::ReadResult = std::variant<BytesNeeded, ResT>

Definition at line 51 of file tls_record_layer_13.h.

Constructor & Destructor Documentation

◆ Record_Layer()

Botan::TLS::Record_Layer::Record_Layer ( Connection_Side side)
explicit

Definition at line 147 of file tls_record_layer_13.cpp.

147 :
148 m_side(side),
149 m_outgoing_record_size_limit(MAX_PLAINTEXT_SIZE + 1 /* content type byte */),
150 m_incoming_record_size_limit(MAX_PLAINTEXT_SIZE + 1 /* content type byte */)
151
152 // RFC 8446 5.1
153 // legacy_record_version: MUST be set to 0x0303 for all records
154 // generated by a TLS 1.3 implementation other than an initial
155 // ClientHello [...], where it MAY also be 0x0301 for compatibility
156 // purposes.
157 //
158 // Additionally, older peers might send other values while requesting a
159 // protocol downgrade. I.e. we need to be able to tolerate/emit legacy
160 // values until we negotiated a TLS 1.3 compliant connection.
161 //
162 // As a client: we may initially emit the compatibility version and
163 // accept a wider range of incoming legacy record versions.
164 // As a server: we start with emitting the specified legacy version of 0x0303
165 // but must also allow a wider range of incoming legacy values.
166 //
167 // Once TLS 1.3 is negotiateed, the implementations will disable these
168 // compatibility modes accordingly or a protocol downgrade will transfer
169 // the marshalling responsibility to our TLS 1.2 implementation.
170 ,
171 m_sending_compat_mode(m_side == Connection_Side::Client),
172 m_receiving_compat_mode(true) {}
@ MAX_PLAINTEXT_SIZE
Definition tls_magic.h:31

References Botan::TLS::MAX_PLAINTEXT_SIZE.

Member Function Documentation

◆ clear_read_buffer()

void Botan::TLS::Record_Layer::clear_read_buffer ( )
inline

Clears any data currently stored in the read buffer. This is typically used for memory cleanup when the peer sent a CloseNotify alert.

Definition at line 82 of file tls_record_layer_13.h.

82 {
83 zap(m_read_buffer);
84 m_read_offset = 0;
85 }
void zap(std::vector< T, Alloc > &vec)
Definition secmem.h:157

References Botan::zap().

◆ copy_data()

void Botan::TLS::Record_Layer::copy_data ( std::span< const uint8_t > data_from_peer)

Reads data that was received by the peer and stores it internally for further processing during the invocation of next_record().

Parameters
data_from_peerThe data to be parsed.

Definition at line 174 of file tls_record_layer_13.cpp.

174 {
175 // Compact consumed data before appending new data
176 BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size());
177 if(m_read_offset > 0) {
178 m_read_buffer.erase(m_read_buffer.begin(), m_read_buffer.begin() + m_read_offset);
179 m_read_offset = 0;
180 }
181
182 m_read_buffer.insert(m_read_buffer.end(), data.begin(), data.end());
183}
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75

References BOTAN_ASSERT_NOMSG.

◆ disable_receiving_compat_mode()

void Botan::TLS::Record_Layer::disable_receiving_compat_mode ( )
inline

Definition at line 104 of file tls_record_layer_13.h.

104{ m_receiving_compat_mode = false; }

◆ disable_sending_compat_mode()

void Botan::TLS::Record_Layer::disable_sending_compat_mode ( )
inline

Definition at line 102 of file tls_record_layer_13.h.

102{ m_sending_compat_mode = false; }

◆ next_record()

Record_Layer::ReadResult< Record > Botan::TLS::Record_Layer::next_record ( Cipher_State * cipher_state = nullptr)

Parses one record off the internal buffer that is being filled using copy_data.

Return value contains either the number of bytes (size_t) needed to proceed with processing TLS records or a single plaintext TLS record content containing higher level protocol or application data.

Parameters
cipher_stateOptional pointer to a Cipher_State instance. If provided, the cipher_state should be ready to decrypt data. Pass nullptr to process plaintext data.

Definition at line 291 of file tls_record_layer_13.cpp.

291 {
292 const auto remaining = m_read_buffer.size() - m_read_offset;
293
294 if(remaining < TLS_HEADER_SIZE) {
295 return TLS_HEADER_SIZE - remaining;
296 }
297
298 const auto header_begin = m_read_buffer.cbegin() + m_read_offset;
299 const auto header_end = header_begin + TLS_HEADER_SIZE;
300
301 // The first received record(s) are likely a client or server hello. To be able to
302 // perform protocol downgrades we must be less vigorous with the record's
303 // legacy version. Hence, `check_tls13_version` is `false` for the first record(s).
304 const TLSPlaintext_Header plaintext_header({header_begin, header_end}, !m_receiving_compat_mode);
305
306 // After the key exchange phase of the handshake is completed and record protection is engaged,
307 // cipher_state is set. At this point, only protected traffic (and CCS) is allowed.
308 //
309 // RFC 8446 2.
310 // - Key Exchange: Establish shared keying material and select the
311 // cryptographic parameters. Everything after this phase is
312 // encrypted.
313 // RFC 8446 5.
314 // An implementation may receive an unencrypted [CCS] at any time
315 if(cipher_state != nullptr && plaintext_header.type() != Record_Type::ApplicationData &&
316 plaintext_header.type() != Record_Type::ChangeCipherSpec &&
317 (!cipher_state->must_expect_unprotected_alert_traffic() || plaintext_header.type() != Record_Type::Alert)) {
318 throw TLS_Exception(Alert::UnexpectedMessage, "unprotected record received where protected traffic was expected");
319 }
320
321 if(remaining < TLS_HEADER_SIZE + plaintext_header.fragment_length()) {
322 return TLS_HEADER_SIZE + plaintext_header.fragment_length() - remaining;
323 }
324
325 const auto fragment_begin = header_end;
326 const auto fragment_end = fragment_begin + plaintext_header.fragment_length();
327
328 if(plaintext_header.type() == Record_Type::ChangeCipherSpec &&
329 !verify_change_cipher_spec(fragment_begin, plaintext_header.fragment_length())) {
330 throw TLS_Exception(Alert::UnexpectedMessage, "malformed change cipher spec record received");
331 }
332
333 Record record(plaintext_header.type(), secure_vector<uint8_t>(fragment_begin, fragment_end));
334 m_read_offset += TLS_HEADER_SIZE + plaintext_header.fragment_length();
335
336 // If all buffered data has been consumed, release the buffer memory
337 // to avoid retaining peak allocation on idle connections.
338 if(m_read_offset == m_read_buffer.size()) {
339 zap(m_read_buffer);
340 m_read_offset = 0;
341 }
342
343 if(record.type == Record_Type::ApplicationData) {
344 if(cipher_state == nullptr) {
345 // This could also mean a misuse of the interface, i.e. failing to provide a valid
346 // cipher_state to parse_records when receiving valid (encrypted) Application Data.
347 throw TLS_Exception(Alert::UnexpectedMessage, "premature Application Data received");
348 }
349
350 if(record.fragment.size() < cipher_state->minimum_decryption_input_length()) {
351 throw TLS_Exception(Alert::BadRecordMac, "incomplete record mac received");
352 }
353
354 if(cipher_state->decrypt_output_length(record.fragment.size()) > m_incoming_record_size_limit) {
355 throw TLS_Exception(Alert::RecordOverflow, "Received an encrypted record that exceeds maximum plaintext size");
356 }
357
358 record.seq_no = cipher_state->decrypt_record_fragment(plaintext_header.serialized(), record.fragment);
359
360 // Remove record padding (RFC 8446 5.4). The TLSInnerPlaintext layout is
361 // content || content_type || zero_padding
362 auto seen_nonzero = CT::Mask<uint8_t>::cleared();
363 uint8_t content_type_byte = 0;
364 size_t content_index = 0;
365 for(size_t i = record.fragment.size(); i-- > 0;) {
366 const uint8_t b = record.fragment[i];
367 const auto byte_is_nonzero = CT::Mask<uint8_t>::expand(b);
368 // Set on the first non-zero byte we encounter scanning right-to-left.
369 const auto first_nonzero = byte_is_nonzero & ~seen_nonzero;
370 content_type_byte = first_nonzero.select(b, content_type_byte);
371 content_index = CT::Mask<size_t>::expand(first_nonzero.value()).select(i, content_index);
372 seen_nonzero |= byte_is_nonzero;
373 }
374
375 if(!seen_nonzero.as_bool()) {
376 // RFC 8446 5.4
377 // If a receiving implementation does not
378 // find a non-zero octet in the cleartext, it MUST terminate the
379 // connection with an "unexpected_message" alert.
380 throw TLS_Exception(Alert::UnexpectedMessage, "No content type found in encrypted record");
381 }
382
383 // hydrate the actual content type from TLSInnerPlaintext
384 record.type = read_record_type(content_type_byte);
385
386 if(record.type == Record_Type::ChangeCipherSpec) {
387 // RFC 8446 5
388 // An implementation [...] which receives a protected change_cipher_spec record MUST
389 // abort the handshake with an "unexpected_message" alert.
390 throw TLS_Exception(Alert::UnexpectedMessage, "protected change cipher spec received");
391 }
392
393 // Truncate to drop the content_type byte and padding. resize() on a
394 // vector of trivially-destructible elements is bookkeeping-only and
395 // does not allocate or iterate over the dropped suffix.
396 record.fragment.resize(content_index);
397
398 // RFC 8446 5.4
399 // Implementations MUST NOT send Handshake and Alert records that have
400 // a zero-length TLSInnerPlaintext.content; if such a message is
401 // received, the receiving implementation MUST terminate the connection
402 // with an "unexpected_message" alert.
403 if(record.fragment.empty() && record.type != Record_Type::ApplicationData) {
404 throw TLS_Exception(Alert::UnexpectedMessage,
405 "Received a protected record with empty TLSInnerPlaintext content");
406 }
407 }
408
409 return record;
410}
static constexpr Mask< T > expand(T v)
Definition ct_utils.h:392
static constexpr Mask< T > cleared()
Definition ct_utils.h:387
@ TLS_HEADER_SIZE
Definition tls_magic.h:26
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:68

References Botan::TLS::Alert, Botan::TLS::ApplicationData, Botan::TLS::ChangeCipherSpec, Botan::CT::Mask< T >::cleared(), Botan::TLS::Cipher_State::decrypt_output_length(), Botan::TLS::Cipher_State::decrypt_record_fragment(), Botan::CT::Mask< T >::expand(), Botan::TLS::Record::fragment, Botan::TLS::Cipher_State::minimum_decryption_input_length(), Botan::TLS::Cipher_State::must_expect_unprotected_alert_traffic(), Botan::TLS::Record::seq_no, Botan::TLS::TLS_HEADER_SIZE, Botan::TLS::Record::type, and Botan::zap().

◆ prepare_records()

std::vector< uint8_t > Botan::TLS::Record_Layer::prepare_records ( Record_Type type,
std::span< const uint8_t > data,
Cipher_State * cipher_state = nullptr ) const

Definition at line 185 of file tls_record_layer_13.cpp.

187 {
188 // RFC 8446 5.
189 // Note that [change_cipher_spec records] may appear at a point at the
190 // handshake where the implementation is expecting protected records.
191 //
192 // RFC 8446 5.
193 // An implementation which receives [...] a protected change_cipher_spec
194 // record MUST abort the handshake [...].
195 //
196 // ... hence, CHANGE_CIPHER_SPEC is never protected, even if a usable cipher
197 // state was passed to this method.
198 const bool protect = cipher_state != nullptr && type != Record_Type::ChangeCipherSpec;
199
200 // RFC 8446 5.1
202 "Application Data records MUST NOT be written to the wire unprotected");
203
204 // RFC 8446 5.1
205 // "MUST NOT sent zero-length fragments of Handshake types"
206 // "a record with an Alert type MUST contain exactly one message" [of non-zero length]
207 // "Zero-length fragments of Application Data MAY be sent"
208 BOTAN_ASSERT(!data.empty() || type == Record_Type::ApplicationData,
209 "zero-length fragments of types other than application data are not allowed");
210
211 if(type == Record_Type::ChangeCipherSpec && !verify_change_cipher_spec(data.begin(), data.size())) {
212 throw Invalid_Argument("TLS 1.3 deprecated CHANGE_CIPHER_SPEC");
213 }
214
215 std::vector<uint8_t> output;
216
217 // RFC 8446 5.2
218 // type: The TLSPlaintext.type value containing the content type of the record.
219 constexpr size_t content_type_tag_length = 1;
220
221 // RFC 8449 4.
222 // When the "record_size_limit" extension is negotiated, an endpoint
223 // MUST NOT generate a protected record with plaintext that is larger
224 // than the RecordSizeLimit value it receives from its peer.
225 // Unprotected messages are not subject to this limit.
226 const size_t max_plaintext_size =
227 (protect) ? m_outgoing_record_size_limit - content_type_tag_length : static_cast<uint16_t>(MAX_PLAINTEXT_SIZE);
228
229 const auto records = std::max((data.size() + max_plaintext_size - 1) / max_plaintext_size, size_t(1));
230 auto output_length = records * TLS_HEADER_SIZE;
231 if(protect) {
232 // n-1 full records of size max_plaintext_size
233 output_length +=
234 (records - 1) * cipher_state->encrypt_output_length(max_plaintext_size + content_type_tag_length);
235 // last record with size of remaining data
236 output_length += cipher_state->encrypt_output_length(data.size() - ((records - 1) * max_plaintext_size) +
237 content_type_tag_length);
238 } else {
239 output_length += data.size();
240 }
241 output.reserve(output_length);
242
243 size_t pt_offset = 0;
244 size_t to_process = data.size();
245
246 // For protected records we need to write at least one encrypted fragment,
247 // even if the plaintext size is zero. This happens only for Application
248 // Data types.
249 BOTAN_ASSERT_NOMSG(to_process != 0 || protect);
250 // NOLINTNEXTLINE(*-avoid-do-while)
251 do {
252 const size_t pt_size = std::min<size_t>(to_process, max_plaintext_size);
253 const size_t ct_size =
254 (!protect) ? pt_size : cipher_state->encrypt_output_length(pt_size + content_type_tag_length);
255 const auto pt_type = (!protect) ? type : Record_Type::ApplicationData;
256
257 // RFC 8446 5.1
258 // MUST be set to 0x0303 for all records generated by a TLS 1.3
259 // implementation other than an initial ClientHello [...], where
260 // it MAY also be 0x0301 for compatibility purposes.
261 const auto record_header = TLSPlaintext_Header(pt_type, ct_size, m_sending_compat_mode).serialized();
262
263 output.insert(output.end(), record_header.cbegin(), record_header.cend());
264
265 auto pt_fragment = data.subspan(pt_offset, pt_size);
266 if(protect) {
267 secure_vector<uint8_t> fragment;
268 fragment.reserve(ct_size);
269
270 // assemble TLSInnerPlaintext structure
271 fragment.insert(fragment.end(), pt_fragment.begin(), pt_fragment.end());
272 fragment.push_back(static_cast<uint8_t>(type));
273 // TODO: zero padding could go here, see RFC 8446 5.4
274
275 cipher_state->encrypt_record_fragment(record_header, fragment);
276 BOTAN_ASSERT_NOMSG(fragment.size() == ct_size);
277
278 output.insert(output.end(), fragment.cbegin(), fragment.cend());
279 } else {
280 output.insert(output.end(), pt_fragment.begin(), pt_fragment.end());
281 }
282
283 pt_offset += pt_size;
284 to_process -= pt_size;
285 } while(to_process > 0);
286
287 BOTAN_ASSERT_NOMSG(output.size() == output_length);
288 return output;
289}
#define BOTAN_ASSERT(expr, assertion_made)
Definition assert.h:62

References Botan::TLS::ApplicationData, BOTAN_ASSERT, BOTAN_ASSERT_NOMSG, Botan::TLS::ChangeCipherSpec, Botan::TLS::Cipher_State::encrypt_output_length(), Botan::TLS::Cipher_State::encrypt_record_fragment(), Botan::TLS::MAX_PLAINTEXT_SIZE, and Botan::TLS::TLS_HEADER_SIZE.

◆ set_record_size_limits()

void Botan::TLS::Record_Layer::set_record_size_limits ( uint16_t outgoing_limit,
uint16_t incoming_limit )

Set the record size limits as negotiated by the "record_size_limit" extension (RFC 8449). The limits refer to the number of plaintext bytes to be encrypted/decrypted – INCLUDING the encrypted content type byte introduced with TLS 1.3. The record size limit is not applied to unprotected records. Incoming records that exceed the set limit will result in a fatal alert.

Parameters
outgoing_limitthe maximal number of plaintext bytes to be sent in a protected record
incoming_limitthe maximal number of plaintext bytes to be accepted in a received protected record

Definition at line 412 of file tls_record_layer_13.cpp.

412 {
413 BOTAN_ARG_CHECK(outgoing_limit >= 64, "Invalid outgoing record size limit");
414 BOTAN_ARG_CHECK(incoming_limit >= 64 && incoming_limit <= MAX_PLAINTEXT_SIZE + 1,
415 "Invalid incoming record size limit");
416
417 // RFC 8449 4.
418 // Even if a larger record size limit is provided by a peer, an endpoint
419 // MUST NOT send records larger than the protocol-defined limit, unless
420 // explicitly allowed by a future TLS version or extension.
421 m_outgoing_record_size_limit = std::min(outgoing_limit, static_cast<uint16_t>(MAX_PLAINTEXT_SIZE + 1));
422 m_incoming_record_size_limit = incoming_limit;
423}
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33

References BOTAN_ARG_CHECK, and Botan::TLS::MAX_PLAINTEXT_SIZE.


The documentation for this class was generated from the following files: