Coverage Report

Created: 2025-04-11 06:34

/src/botan/src/lib/tls/tls13/tls_transcript_hash_13.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
* TLS transcript hash implementation for TLS 1.3
3
* (C) 2022 Jack Lloyd
4
*     2022 Hannes Rantzsch, René Meusel - neXenio GmbH
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8
9
#include <botan/internal/tls_transcript_hash_13.h>
10
11
#include <botan/tls_exceptn.h>
12
#include <botan/tls_messages.h>
13
#include <botan/internal/tls_reader.h>
14
15
#include <utility>
16
17
namespace Botan::TLS {
18
19
6.03k
Transcript_Hash_State::Transcript_Hash_State(std::string_view algo_spec) {
20
6.03k
   set_algorithm(algo_spec);
21
6.03k
}
22
23
Transcript_Hash_State::Transcript_Hash_State(const Transcript_Hash_State& other) :
24
0
      m_hash((other.m_hash != nullptr) ? other.m_hash->copy_state() : nullptr),
25
0
      m_unprocessed_transcript(other.m_unprocessed_transcript),
26
0
      m_current(other.m_current),
27
0
      m_previous(other.m_previous),
28
0
      m_truncated(other.m_truncated) {}
29
30
Transcript_Hash_State Transcript_Hash_State::recreate_after_hello_retry_request(
31
0
   std::string_view algo_spec, const Transcript_Hash_State& prev_transcript_hash_state) {
32
   // make sure that we have seen exactly 'client_hello' and 'hello_retry_request'
33
   // before re-creating the transcript hash state
34
0
   BOTAN_STATE_CHECK(prev_transcript_hash_state.m_hash == nullptr);
35
0
   BOTAN_STATE_CHECK(prev_transcript_hash_state.m_unprocessed_transcript.size() == 2);
36
37
0
   Transcript_Hash_State ths(algo_spec);
38
39
0
   const auto& client_hello_1 = prev_transcript_hash_state.m_unprocessed_transcript.front();
40
0
   const auto& hello_retry_request = prev_transcript_hash_state.m_unprocessed_transcript.back();
41
42
0
   const size_t hash_length = ths.m_hash->output_length();
43
0
   BOTAN_ASSERT_NOMSG(hash_length < 256);
44
45
   // RFC 8446 4.4.1
46
   //    [...], when the server responds to a ClientHello with a HelloRetryRequest,
47
   //    the value of ClientHello1 is replaced with a special synthetic handshake
48
   //    message of handshake type "message_hash" [(0xFE)] containing:
49
0
   std::vector<uint8_t> message_hash;
50
0
   message_hash.reserve(4 + hash_length);
51
0
   message_hash.push_back(0xFE /* message type 'message_hash' RFC 8446 4. */);
52
0
   message_hash.push_back(0x00);
53
0
   message_hash.push_back(0x00);
54
0
   message_hash.push_back(static_cast<uint8_t>(hash_length));
55
0
   message_hash += ths.m_hash->process(client_hello_1);
56
57
0
   ths.update(message_hash);
58
0
   ths.update(hello_retry_request);
59
60
0
   return ths;
61
0
}
62
63
namespace {
64
65
// TODO: This is a massive code duplication of the client hello parsing code,
66
//       as well as basic parsing of extensions. We should resolve this.
67
//
68
// Ad-hoc idea: When parsing the production objects, we could keep markers into
69
//              the original buffer. E.g. the PSK extensions would keep its off-
70
//              set into the entire client hello buffer. Using that offset we
71
//              could quickly identify the offset of the binders list slice the
72
//              buffer without re-parsing it.
73
//
74
// Finds the truncation offset in a serialization of Client Hello as defined in
75
// RFC 8446 4.2.11.2 used for the calculation of PSK binder MACs.
76
6.36k
size_t find_client_hello_truncation_mark(std::span<const uint8_t> client_hello) {
77
6.36k
   TLS_Data_Reader reader("Client Hello Truncation", client_hello);
78
79
   // handshake message type
80
6.36k
   BOTAN_ASSERT_NOMSG(reader.get_byte() == static_cast<uint8_t>(Handshake_Type::ClientHello));
81
82
   // message length
83
6.36k
   reader.discard_next(3);
84
85
   // legacy version
86
6.36k
   reader.discard_next(2);
87
88
   // random
89
6.36k
   reader.discard_next(32);
90
91
   // session ID
92
6.36k
   const auto session_id_length = reader.get_byte();
93
6.36k
   reader.discard_next(session_id_length);
94
95
   // TODO: DTLS contains a hello_cookie in this location
96
   //       Currently we don't support DTLS 1.3
97
98
   // cipher suites
99
6.36k
   const auto ciphersuites_length = reader.get_uint16_t();
100
6.36k
   reader.discard_next(ciphersuites_length);
101
102
   // compression methods
103
6.36k
   const auto compression_methods_length = reader.get_byte();
104
6.36k
   reader.discard_next(compression_methods_length);
105
106
   // extensions
107
6.36k
   const auto extensions_length = reader.get_uint16_t();
108
6.36k
   const auto extensions_offset = reader.read_so_far();
109
16.0k
   while(reader.has_remaining() && reader.read_so_far() - extensions_offset < extensions_length) {
110
10.3k
      const auto ext_type = static_cast<Extension_Code>(reader.get_uint16_t());
111
10.3k
      const auto ext_length = reader.get_uint16_t();
112
113
      // skip over all extensions, finding the PSK extension to be truncated
114
10.3k
      if(ext_type != Extension_Code::PresharedKey) {
115
9.64k
         reader.discard_next(ext_length);
116
9.64k
         continue;
117
9.64k
      }
118
119
      // PSK identities list
120
743
      const auto identities_length = reader.get_uint16_t();
121
743
      reader.discard_next(identities_length);
122
123
      // check that only the binders are left in the buffer...
124
743
      const auto binders_length = reader.peek_uint16_t();
125
743
      if(binders_length != reader.remaining_bytes() - 2 /* binders_length */) {
126
27
         throw TLS_Exception(Alert::IllegalParameter,
127
27
                             "Failed to truncate Client Hello that doesn't end on the PSK binders list");
128
27
      }
129
130
      // the reader now points to the truncation point
131
716
      break;
132
743
   }
133
134
   // if no PSK extension was found, this will point to the end of the buffer
135
6.34k
   return reader.read_so_far();
136
6.36k
}
137
138
26.9k
std::vector<uint8_t> read_hash_state(std::unique_ptr<HashFunction>& hash) {
139
   // Botan does not support finalizing a HashFunction without resetting
140
   // the internal state of the hash. Hence we first copy the internal
141
   // state and then finalize the transient HashFunction.
142
26.9k
   return hash->copy_state()->final_stdvec();
143
26.9k
}
144
145
}  // namespace
146
147
30.2k
void Transcript_Hash_State::update(std::span<const uint8_t> serialized_message_s) {
148
30.2k
   auto serialized_message = serialized_message_s.data();
149
30.2k
   auto serialized_message_length = serialized_message_s.size();
150
30.2k
   if(m_hash != nullptr) {
151
26.2k
      auto truncation_mark = serialized_message_length;
152
153
      // Check whether we should generate a truncated hash for supporting PSK
154
      // binder calculation or verification. See RFC 8446 4.2.11.2.
155
26.2k
      if(serialized_message_length > 0 && *serialized_message == static_cast<uint8_t>(Handshake_Type::ClientHello)) {
156
6.36k
         truncation_mark = find_client_hello_truncation_mark(serialized_message_s);
157
6.36k
      }
158
159
26.2k
      if(truncation_mark < serialized_message_length) {
160
792
         m_hash->update(serialized_message, truncation_mark);
161
792
         m_truncated = read_hash_state(m_hash);
162
792
         m_hash->update(serialized_message + truncation_mark, serialized_message_length - truncation_mark);
163
25.5k
      } else {
164
25.5k
         m_truncated.clear();
165
25.5k
         m_hash->update(serialized_message, serialized_message_length);
166
25.5k
      }
167
168
26.2k
      m_previous = std::exchange(m_current, read_hash_state(m_hash));
169
26.2k
   } else {
170
3.93k
      m_unprocessed_transcript.push_back(
171
3.93k
         std::vector(serialized_message, serialized_message + serialized_message_length));
172
3.93k
   }
173
30.2k
}
174
175
0
const Transcript_Hash& Transcript_Hash_State::current() const {
176
0
   BOTAN_STATE_CHECK(!m_current.empty());
177
0
   return m_current;
178
0
}
179
180
0
const Transcript_Hash& Transcript_Hash_State::previous() const {
181
0
   BOTAN_STATE_CHECK(!m_previous.empty());
182
0
   return m_previous;
183
0
}
184
185
0
const Transcript_Hash& Transcript_Hash_State::truncated() const {
186
0
   BOTAN_STATE_CHECK(!m_truncated.empty());
187
0
   return m_truncated;
188
0
}
189
190
6.03k
void Transcript_Hash_State::set_algorithm(std::string_view algo_spec) {
191
6.03k
   BOTAN_STATE_CHECK(m_hash == nullptr || m_hash->name() == algo_spec);
192
6.03k
   if(m_hash != nullptr) {
193
0
      return;
194
0
   }
195
196
6.03k
   m_hash = HashFunction::create_or_throw(algo_spec);
197
6.03k
   for(const auto& msg : m_unprocessed_transcript) {
198
0
      update(msg);
199
0
   }
200
6.03k
   m_unprocessed_transcript.clear();
201
6.03k
}
202
203
0
Transcript_Hash_State Transcript_Hash_State::clone() const {
204
0
   return *this;
205
0
}
206
207
}  // namespace Botan::TLS