Botan 3.13.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, std::shared_ptr< const Policy > policy)
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 48 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 53 of file tls_record_layer_13.h.

Constructor & Destructor Documentation

◆ Record_Layer()

Botan::TLS::Record_Layer::Record_Layer ( Connection_Side side,
std::shared_ptr< const Policy > policy )

Definition at line 149 of file tls_record_layer_13.cpp.

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

References BOTAN_ASSERT_NONNULL, and 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 84 of file tls_record_layer_13.h.

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

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 179 of file tls_record_layer_13.cpp.

179 {
180 // Compact consumed data before appending new data
181 BOTAN_ASSERT_NOMSG(m_read_offset <= m_read_buffer.size());
182 if(m_read_offset > 0) {
183 m_read_buffer.erase(m_read_buffer.begin(), m_read_buffer.begin() + m_read_offset);
184 m_read_offset = 0;
185 }
186
187 m_read_buffer.insert(m_read_buffer.end(), data.begin(), data.end());
188}
#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 106 of file tls_record_layer_13.h.

106{ m_receiving_compat_mode = false; }

◆ disable_sending_compat_mode()

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

Definition at line 104 of file tls_record_layer_13.h.

104{ 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 315 of file tls_record_layer_13.cpp.

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

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 190 of file tls_record_layer_13.cpp.

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

References Botan::TLS::ApplicationData, BOTAN_ASSERT, BOTAN_ASSERT_IMPLICATION, 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 443 of file tls_record_layer_13.cpp.

443 {
444 BOTAN_ARG_CHECK(outgoing_limit >= 64, "Invalid outgoing record size limit");
445 BOTAN_ARG_CHECK(incoming_limit >= 64 && incoming_limit <= MAX_PLAINTEXT_SIZE + 1,
446 "Invalid incoming record size limit");
447
448 // RFC 8449 4.
449 // Even if a larger record size limit is provided by a peer, an endpoint
450 // MUST NOT send records larger than the protocol-defined limit, unless
451 // explicitly allowed by a future TLS version or extension.
452 m_outgoing_record_size_limit = std::min(outgoing_limit, static_cast<uint16_t>(MAX_PLAINTEXT_SIZE + 1));
453 m_incoming_record_size_limit = incoming_limit;
454}
#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: