Botan 3.9.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 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)
explicit

Definition at line 146 of file tls_record_layer_13.cpp.

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

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 84 of file tls_record_layer_13.h.

84{ zap(m_read_buffer); }
void zap(std::vector< T, Alloc > &vec)
Definition secmem.h:134

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

173 {
174 m_read_buffer.insert(m_read_buffer.end(), data.begin(), data.end());
175}

◆ disable_receiving_compat_mode()

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

Definition at line 103 of file tls_record_layer_13.h.

103{ m_receiving_compat_mode = false; }

◆ disable_sending_compat_mode()

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

Definition at line 101 of file tls_record_layer_13.h.

101{ 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 283 of file tls_record_layer_13.cpp.

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

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.

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

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

372 {
373 BOTAN_ARG_CHECK(outgoing_limit >= 64, "Invalid outgoing record size limit");
374 BOTAN_ARG_CHECK(incoming_limit >= 64 && incoming_limit <= MAX_PLAINTEXT_SIZE + 1,
375 "Invalid incoming record size limit");
376
377 // RFC 8449 4.
378 // Even if a larger record size limit is provided by a peer, an endpoint
379 // MUST NOT send records larger than the protocol-defined limit, unless
380 // explicitly allowed by a future TLS version or extension.
381 m_outgoing_record_size_limit = std::min(outgoing_limit, static_cast<uint16_t>(MAX_PLAINTEXT_SIZE + 1));
382 m_incoming_record_size_limit = incoming_limit;
383}
#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: