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