• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 22004741794

13 Feb 2026 10:16PM UTC coverage: 90.065%. Remained the same
22004741794

push

github

web-flow
Merge pull request #5326 from randombit/jack/fwd-declare-hash-in-tls-transcript-hash

Avoid including hash.h into tls_transcript_hash_13.h

102229 of 113506 relevant lines covered (90.06%)

11465602.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.88
/src/lib/tls/tls13/tls_transcript_hash_13.cpp
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/hash.h>
12
#include <botan/tls_exceptn.h>
13
#include <botan/tls_extensions.h>
14
#include <botan/internal/tls_reader.h>
15

16
#include <utility>
17

18
namespace Botan::TLS {
19

20
Transcript_Hash_State::Transcript_Hash_State(std::string_view algo_spec) {
173✔
21
   set_algorithm(algo_spec);
173✔
22
}
173✔
23

24
Transcript_Hash_State::Transcript_Hash_State() = default;
3,259✔
25

26
Transcript_Hash_State::~Transcript_Hash_State() = default;
5,692✔
27

28
Transcript_Hash_State::Transcript_Hash_State(Transcript_Hash_State&& other) noexcept = default;
×
29
Transcript_Hash_State& Transcript_Hash_State::operator=(Transcript_Hash_State&& other) noexcept = default;
111✔
30

31
Transcript_Hash_State::Transcript_Hash_State(const Transcript_Hash_State& other) :
130✔
32
      m_hash((other.m_hash != nullptr) ? other.m_hash->copy_state() : nullptr),
130✔
33
      m_unprocessed_transcript(other.m_unprocessed_transcript),
130✔
34
      m_current(other.m_current),
130✔
35
      m_previous(other.m_previous),
130✔
36
      m_truncated(other.m_truncated) {}
130✔
37

38
Transcript_Hash_State Transcript_Hash_State::recreate_after_hello_retry_request(
112✔
39
   std::string_view algo_spec, const Transcript_Hash_State& prev_transcript_hash_state) {
40
   // make sure that we have seen exactly 'client_hello' and 'hello_retry_request'
41
   // before re-creating the transcript hash state
42
   BOTAN_STATE_CHECK(prev_transcript_hash_state.m_hash == nullptr);
112✔
43
   BOTAN_STATE_CHECK(prev_transcript_hash_state.m_unprocessed_transcript.size() == 2);
112✔
44

45
   Transcript_Hash_State transcript_hash(algo_spec);
112✔
46

47
   const auto& client_hello_1 = prev_transcript_hash_state.m_unprocessed_transcript.front();
112✔
48
   const auto& hello_retry_request = prev_transcript_hash_state.m_unprocessed_transcript.back();
112✔
49

50
   const size_t hash_length = transcript_hash.m_hash->output_length();
112✔
51
   BOTAN_ASSERT_NOMSG(hash_length < 256);
112✔
52

53
   // RFC 8446 4.4.1
54
   //    [...], when the server responds to a ClientHello with a HelloRetryRequest,
55
   //    the value of ClientHello1 is replaced with a special synthetic handshake
56
   //    message of handshake type "message_hash" [(0xFE)] containing:
57
   std::vector<uint8_t> message_hash;
112✔
58
   message_hash.reserve(4 + hash_length);
112✔
59
   message_hash.push_back(0xFE /* message type 'message_hash' RFC 8446 4. */);
112✔
60
   message_hash.push_back(0x00);
112✔
61
   message_hash.push_back(0x00);
112✔
62
   message_hash.push_back(static_cast<uint8_t>(hash_length));
112✔
63
   message_hash += transcript_hash.m_hash->process(client_hello_1);
112✔
64

65
   transcript_hash.update(message_hash);
112✔
66
   transcript_hash.update(hello_retry_request);
112✔
67

68
   return transcript_hash;
112✔
69
}
112✔
70

71
namespace {
72

73
// TODO: This is a massive code duplication of the client hello parsing code,
74
//       as well as basic parsing of extensions. We should resolve this.
75
//
76
// Ad-hoc idea: When parsing the production objects, we could keep markers into
77
//              the original buffer. E.g. the PSK extensions would keep its off-
78
//              set into the entire client hello buffer. Using that offset we
79
//              could quickly identify the offset of the binders list slice the
80
//              buffer without re-parsing it.
81
//
82
// Finds the truncation offset in a serialization of Client Hello as defined in
83
// RFC 8446 4.2.11.2 used for the calculation of PSK binder MACs.
84
size_t find_client_hello_truncation_mark(std::span<const uint8_t> client_hello) {
944✔
85
   TLS_Data_Reader reader("Client Hello Truncation", client_hello);
944✔
86

87
   // handshake message type
88
   BOTAN_ASSERT_NOMSG(reader.get_byte() == static_cast<uint8_t>(Handshake_Type::ClientHello));
944✔
89

90
   // message length
91
   reader.discard_next(3);
944✔
92

93
   // legacy version
94
   reader.discard_next(2);
944✔
95

96
   // random
97
   reader.discard_next(32);
944✔
98

99
   // session ID
100
   const auto session_id_length = reader.get_byte();
944✔
101
   reader.discard_next(session_id_length);
944✔
102

103
   // TODO: DTLS contains a hello_cookie in this location
104
   //       Currently we don't support DTLS 1.3
105

106
   // cipher suites
107
   const auto ciphersuites_length = reader.get_uint16_t();
944✔
108
   reader.discard_next(ciphersuites_length);
944✔
109

110
   // compression methods
111
   const auto compression_methods_length = reader.get_byte();
944✔
112
   reader.discard_next(compression_methods_length);
944✔
113

114
   // extensions
115
   const auto extensions_length = reader.get_uint16_t();
944✔
116
   const auto extensions_offset = reader.read_so_far();
944✔
117
   while(reader.has_remaining() && reader.read_so_far() - extensions_offset < extensions_length) {
11,513✔
118
      const auto ext_type = static_cast<Extension_Code>(reader.get_uint16_t());
10,861✔
119
      const auto ext_length = reader.get_uint16_t();
10,861✔
120

121
      // skip over all extensions, finding the PSK extension to be truncated
122
      if(ext_type != Extension_Code::PresharedKey) {
10,861✔
123
         reader.discard_next(ext_length);
10,569✔
124
         continue;
10,569✔
125
      }
126

127
      // PSK identities list
128
      const auto identities_length = reader.get_uint16_t();
292✔
129
      reader.discard_next(identities_length);
292✔
130

131
      // check that only the binders are left in the buffer...
132
      const auto binders_length = reader.peek_uint16_t();
292✔
133
      if(binders_length != reader.remaining_bytes() - 2 /* binders_length */) {
292✔
134
         throw TLS_Exception(Alert::IllegalParameter,
×
135
                             "Failed to truncate Client Hello that doesn't end on the PSK binders list");
×
136
      }
137

138
      // the reader now points to the truncation point
139
      break;
140
   }
141

142
   // if no PSK extension was found, this will point to the end of the buffer
143
   return reader.read_so_far();
944✔
144
}
145

146
std::vector<uint8_t> read_hash_state(std::unique_ptr<HashFunction>& hash) {
5,883✔
147
   // Botan does not support finalizing a HashFunction without resetting
148
   // the internal state of the hash. Hence we first copy the internal
149
   // state and then finalize the transient HashFunction.
150
   return hash->copy_state()->final_stdvec();
11,766✔
151
}
152

153
}  // namespace
154

155
void Transcript_Hash_State::update(std::span<const uint8_t> serialized_message_s) {
8,603✔
156
   const auto* serialized_message = serialized_message_s.data();
8,603✔
157
   auto serialized_message_length = serialized_message_s.size();
8,603✔
158
   if(m_hash != nullptr) {
8,603✔
159
      auto truncation_mark = serialized_message_length;
5,591✔
160

161
      // Check whether we should generate a truncated hash for supporting PSK
162
      // binder calculation or verification. See RFC 8446 4.2.11.2.
163
      if(serialized_message_length > 0 && *serialized_message == static_cast<uint8_t>(Handshake_Type::ClientHello)) {
5,591✔
164
         truncation_mark = find_client_hello_truncation_mark(serialized_message_s);
944✔
165
      }
166

167
      if(truncation_mark < serialized_message_length) {
5,591✔
168
         m_hash->update(serialized_message, truncation_mark);
292✔
169
         m_truncated = read_hash_state(m_hash);
292✔
170
         m_hash->update(serialized_message + truncation_mark, serialized_message_length - truncation_mark);
292✔
171
      } else {
172
         m_truncated.clear();
5,299✔
173
         m_hash->update(serialized_message, serialized_message_length);
5,299✔
174
      }
175

176
      m_previous = std::exchange(m_current, read_hash_state(m_hash));
5,591✔
177
   } else {
178
      m_unprocessed_transcript.push_back(
6,024✔
179
         std::vector(serialized_message, serialized_message + serialized_message_length));
6,024✔
180
   }
181
}
8,603✔
182

183
const Transcript_Hash& Transcript_Hash_State::current() const {
3,039✔
184
   BOTAN_STATE_CHECK(!m_current.empty());
3,039✔
185
   return m_current;
3,026✔
186
}
187

188
const Transcript_Hash& Transcript_Hash_State::previous() const {
1,112✔
189
   BOTAN_STATE_CHECK(!m_previous.empty());
1,112✔
190
   return m_previous;
1,108✔
191
}
192

193
const Transcript_Hash& Transcript_Hash_State::truncated() const {
214✔
194
   BOTAN_STATE_CHECK(!m_truncated.empty());
214✔
195
   return m_truncated;
213✔
196
}
197

198
void Transcript_Hash_State::set_algorithm(std::string_view algo_spec) {
1,095✔
199
   BOTAN_STATE_CHECK(m_hash == nullptr || m_hash->name() == algo_spec);
1,287✔
200
   if(m_hash != nullptr) {
1,093✔
201
      return;
202
   }
203

204
   m_hash = HashFunction::create_or_throw(algo_spec);
999✔
205
   for(const auto& msg : m_unprocessed_transcript) {
2,214✔
206
      update(msg);
1,215✔
207
   }
208
   m_unprocessed_transcript.clear();
999✔
209
}
210

211
Transcript_Hash_State Transcript_Hash_State::clone() const {
130✔
212
   return *this;
130✔
213
}
214

215
}  // namespace Botan::TLS
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc