Botan 3.5.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)

Definition at line 143 of file tls_record_layer_13.cpp.

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

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{ zap(m_read_buffer); }
void zap(std::vector< T, Alloc > &vec)
Definition secmem.h:117

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

170 {
171 m_read_buffer.insert(m_read_buffer.end(), data.begin(), data.end());
172}

Referenced by Botan::TLS::Channel_Impl_13::from_peer().

◆ disable_receiving_compat_mode()

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

Definition at line 101 of file tls_record_layer_13.h.

101{ m_receiving_compat_mode = false; }

Referenced by Botan::TLS::Channel_Impl_13::from_peer().

◆ disable_sending_compat_mode()

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

Definition at line 99 of file tls_record_layer_13.h.

99{ 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 279 of file tls_record_layer_13.cpp.

279 {
280 if(m_read_buffer.size() < TLS_HEADER_SIZE) {
281 return TLS_HEADER_SIZE - m_read_buffer.size();
282 }
283
284 const auto header_begin = m_read_buffer.cbegin();
285 const auto header_end = header_begin + TLS_HEADER_SIZE;
286
287 // The first received record(s) are likely a client or server hello. To be able to
288 // perform protocol downgrades we must be less vigorous with the record's
289 // legacy version. Hence, `check_tls13_version` is `false` for the first record(s).
290 TLSPlaintext_Header plaintext_header({header_begin, header_end}, !m_receiving_compat_mode);
291
292 // After the key exchange phase of the handshake is completed and record protection is engaged,
293 // cipher_state is set. At this point, only protected traffic (and CCS) is allowed.
294 //
295 // RFC 8446 2.
296 // - Key Exchange: Establish shared keying material and select the
297 // cryptographic parameters. Everything after this phase is
298 // encrypted.
299 // RFC 8446 5.
300 // An implementation may receive an unencrypted [CCS] at any time
301 if(cipher_state != nullptr && plaintext_header.type() != Record_Type::ApplicationData &&
302 plaintext_header.type() != Record_Type::ChangeCipherSpec &&
303 (!cipher_state->must_expect_unprotected_alert_traffic() || plaintext_header.type() != Record_Type::Alert)) {
304 throw TLS_Exception(Alert::UnexpectedMessage, "unprotected record received where protected traffic was expected");
305 }
306
307 if(m_read_buffer.size() < TLS_HEADER_SIZE + plaintext_header.fragment_length()) {
308 return TLS_HEADER_SIZE + plaintext_header.fragment_length() - m_read_buffer.size();
309 }
310
311 const auto fragment_begin = header_end;
312 const auto fragment_end = fragment_begin + plaintext_header.fragment_length();
313
314 if(plaintext_header.type() == Record_Type::ChangeCipherSpec &&
315 !verify_change_cipher_spec(fragment_begin, plaintext_header.fragment_length())) {
316 throw TLS_Exception(Alert::UnexpectedMessage, "malformed change cipher spec record received");
317 }
318
319 Record record(plaintext_header.type(), secure_vector<uint8_t>(fragment_begin, fragment_end));
320 m_read_buffer.erase(header_begin, fragment_end);
321
322 if(record.type == Record_Type::ApplicationData) {
323 if(cipher_state == nullptr) {
324 // This could also mean a misuse of the interface, i.e. failing to provide a valid
325 // cipher_state to parse_records when receiving valid (encrypted) Application Data.
326 throw TLS_Exception(Alert::UnexpectedMessage, "premature Application Data received");
327 }
328
329 if(record.fragment.size() < cipher_state->minimum_decryption_input_length()) {
330 throw TLS_Exception(Alert::BadRecordMac, "incomplete record mac received");
331 }
332
333 if(cipher_state->decrypt_output_length(record.fragment.size()) > m_incoming_record_size_limit) {
334 throw TLS_Exception(Alert::RecordOverflow, "Received an encrypted record that exceeds maximum plaintext size");
335 }
336
337 record.seq_no = cipher_state->decrypt_record_fragment(plaintext_header.serialized(), record.fragment);
338
339 // Remove record padding (RFC 8446 5.4).
340 const auto end_of_content =
341 std::find_if(record.fragment.crbegin(), record.fragment.crend(), [](auto byte) { return byte != 0x00; });
342
343 if(end_of_content == record.fragment.crend()) {
344 // RFC 8446 5.4
345 // If a receiving implementation does not
346 // find a non-zero octet in the cleartext, it MUST terminate the
347 // connection with an "unexpected_message" alert.
348 throw TLS_Exception(Alert::UnexpectedMessage, "No content type found in encrypted record");
349 }
350
351 // hydrate the actual content type from TLSInnerPlaintext
352 record.type = read_record_type(*end_of_content);
353
354 if(record.type == Record_Type::ChangeCipherSpec) {
355 // RFC 8446 5
356 // An implementation [...] which receives a protected change_cipher_spec record MUST
357 // abort the handshake with an "unexpected_message" alert.
358 throw TLS_Exception(Alert::UnexpectedMessage, "protected change cipher spec received");
359 }
360
361 // erase content type and padding
362 record.fragment.erase((end_of_content + 1).base(), record.fragment.cend());
363 }
364
365 return record;
366}
@ TLS_HEADER_SIZE
Definition tls_magic.h:25
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:61

References Botan::TLS::Alert, Botan::TLS::ApplicationData, Botan::TLS::ChangeCipherSpec, Botan::TLS::Cipher_State::decrypt_output_length(), Botan::TLS::Cipher_State::decrypt_record_fragment(), 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, and Botan::TLS::Record::type.

Referenced by Botan::TLS::Channel_Impl_13::from_peer().

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

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

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

368 {
369 BOTAN_ARG_CHECK(outgoing_limit >= 64, "Invalid outgoing record size limit");
370 BOTAN_ARG_CHECK(incoming_limit >= 64 && incoming_limit <= MAX_PLAINTEXT_SIZE + 1,
371 "Invalid incoming record size limit");
372
373 // RFC 8449 4.
374 // Even if a larger record size limit is provided by a peer, an endpoint
375 // MUST NOT send records larger than the protocol-defined limit, unless
376 // explicitly allowed by a future TLS version or extension.
377 m_outgoing_record_size_limit = std::min(outgoing_limit, static_cast<uint16_t>(MAX_PLAINTEXT_SIZE + 1));
378 m_incoming_record_size_limit = incoming_limit;
379}
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:29

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

Referenced by Botan::TLS::Channel_Impl_13::set_record_size_limits().


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