Repository navigation
Conversation
23f7ad1 to
b78a90e
Compare
fdf7c50 to
a55f4f3
Compare
Cipher_State becomes a (non-instantiable) base class that implements the (D)TLS 1.3 key schedule (RFC 9846 7.1). Record protection is moved into a new TLS_Cipher_State subclass. The base class's public API remains unchanged apart from hosting neither protect_record() nor deprotect_record() anymore. Those are provided by the concrete implementations, currently TLS only. Co-Authored-By: Amos Treiber <amos.treiber@rohde-schwarz.com>
All cryptographic aspects that belong to one generation of traffic keys (AEAD instance, IV, sequence number, traffic secret and, for handshake epochs, the Finished key) are now captured in the internal structure Cipher_State::Epoch. Previously, those were individual members of Cipher_State. The base class now accesses the epochs through a few virtual methods and leaves their management to the concrete subclasses. TLS_Cipher_State keeps one current epoch per direction and simply rolls it over whenever new traffic secrets are derived. Co-Authored-By: Amos Treiber <amos.treiber@rohde-schwarz.com>
The low-level aspects of protecting and deprotecting a record are moved out of TLS_Cipher_State::protect_record() and ::deprotect_record() into protected static helpers of Cipher_State that operate on a given Epoch. This is done to allow re-using these aspects in an upcoming DTLS variant. Co-Authored-By: Amos Treiber <amos.treiber@rohde-schwarz.com>
a55f4f3 to
73b74c8
Compare
|
|
||
| const auto hkdf_label = | ||
| concat<secure_vector<uint8_t>>(store_be(static_cast<uint16_t>(length)), | ||
| store_be(static_cast<uint8_t>(m_expansion_label_prefix.size() + label.size())), |
There was a problem hiding this comment.
This drops the check on the label lengths (was BOTAN_ARG_CHECK(prefix.size() + label.size() <= 255, "label too large");)
| } | ||
|
|
||
| uint64_t Cipher_State::records_decrypted_with_current_key() const { | ||
| return latest_read_epoch().sequence_number; |
There was a problem hiding this comment.
I think this causes a half-closed connection to fail; peer sends close_notify alert, we remove the read epoch, then the send path checks this value [to see if it should try to request a peer KeyUpdate] which then fails with an exception.
| std::optional<Epoch> m_read_epoch; | ||
| }; | ||
|
|
||
| inline TLS_Cipher_State* as_tls_cipher_state(Cipher_State* cs) { |
There was a problem hiding this comment.
This introduces a dynamic cast call per record and I'm not sure there is any need for it - couldn't the record layer just hold the correct type directly?
| } | ||
|
|
||
| BOTAN_ASSERT_NOMSG((m_encrypt == nullptr) == (m_decrypt == nullptr)); | ||
| BOTAN_ASSERT_NOMSG(has_write_epoch() == has_read_epoch()); |
There was a problem hiding this comment.
This is analagous to the earlier assert, but different in the case of a half-close
This makes
TLS::Cipher_Statean abstract base class that implements TLS' key schedule and provides low-level building blocks for record (de)protection. The base class is derived byTLS::TLS_Cipher_Statethat adds TLS-specific record (de)protection based on that.Also, this patch introduces an
Epochstruct that holds the unidirectional cryptographic infrastructure for generations of send/recv traffic keys. NaturallyTLS_Cipher_Statejust handles singleton Epochs, but the upcomingDTLS_Cipher_State(#5000) will maintain historic Epochs for handling retransmissions and out-of-order record delivery.The agents did a decent job to split this up into reviewable commits:
TLS_Cipher_State. The *.cpp has some large hunks, but that's essentially just moving code around.Epochstructure