Botan 3.11.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/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
18namespace Botan::TLS {
19
21 set_algorithm(algo_spec);
22}
23
25
27
30
32 m_hash((other.m_hash != nullptr) ? other.m_hash->copy_state() : nullptr),
33 m_unprocessed_transcript(other.m_unprocessed_transcript),
34 m_current(other.m_current),
35 m_previous(other.m_previous),
36 m_truncated(other.m_truncated) {}
37
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);
43 BOTAN_STATE_CHECK(prev_transcript_hash_state.m_unprocessed_transcript.size() == 2);
44
45 Transcript_Hash_State transcript_hash(algo_spec);
46
47 const auto& client_hello_1 = prev_transcript_hash_state.m_unprocessed_transcript.front();
48 const auto& hello_retry_request = prev_transcript_hash_state.m_unprocessed_transcript.back();
49
50 const size_t hash_length = transcript_hash.m_hash->output_length();
51 BOTAN_ASSERT_NOMSG(hash_length < 256);
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;
58 message_hash.reserve(4 + hash_length);
59 message_hash.push_back(0xFE /* message type 'message_hash' RFC 8446 4. */);
60 message_hash.push_back(0x00);
61 message_hash.push_back(0x00);
62 message_hash.push_back(static_cast<uint8_t>(hash_length));
63 message_hash += transcript_hash.m_hash->process(client_hello_1);
64
65 transcript_hash.update(message_hash);
66 transcript_hash.update(hello_retry_request);
67
68 return transcript_hash;
69}
70
71namespace {
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.
84size_t find_client_hello_truncation_mark(std::span<const uint8_t> client_hello) {
85 TLS_Data_Reader reader("Client Hello Truncation", client_hello);
86
87 // handshake message type
88 BOTAN_ASSERT_NOMSG(reader.get_byte() == static_cast<uint8_t>(Handshake_Type::ClientHello));
89
90 // message length
91 reader.discard_next(3);
92
93 // legacy version
94 reader.discard_next(2);
95
96 // random
97 reader.discard_next(32);
98
99 // session ID
100 const auto session_id_length = reader.get_byte();
101 reader.discard_next(session_id_length);
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();
108 reader.discard_next(ciphersuites_length);
109
110 // compression methods
111 const auto compression_methods_length = reader.get_byte();
112 reader.discard_next(compression_methods_length);
113
114 // extensions
115 const auto extensions_length = reader.get_uint16_t();
116 const auto extensions_offset = reader.read_so_far();
117 while(reader.has_remaining() && reader.read_so_far() - extensions_offset < extensions_length) {
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 reader.discard_next(ext_length);
124 continue;
125 }
126
127 // PSK identities list
128 const auto identities_length = reader.get_uint16_t();
129 reader.discard_next(identities_length);
130
131 // check that only the binders are left in the buffer...
132 const auto binders_length = reader.peek_uint16_t();
133 if(binders_length != reader.remaining_bytes() - 2 /* binders_length */) {
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();
144}
145
146std::vector<uint8_t> read_hash_state(std::unique_ptr<HashFunction>& hash) {
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();
151}
152
153} // namespace
154
155void Transcript_Hash_State::update(std::span<const uint8_t> serialized_message_s) {
156 const auto* serialized_message = serialized_message_s.data();
157 auto serialized_message_length = serialized_message_s.size();
158 if(m_hash != nullptr) {
159 auto truncation_mark = serialized_message_length;
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)) {
164 truncation_mark = find_client_hello_truncation_mark(serialized_message_s);
165 }
166
167 if(truncation_mark < serialized_message_length) {
168 m_hash->update(serialized_message, truncation_mark);
169 m_truncated = read_hash_state(m_hash);
170 m_hash->update(serialized_message + truncation_mark, serialized_message_length - truncation_mark);
171 } else {
172 m_truncated.clear();
173 m_hash->update(serialized_message, serialized_message_length);
174 }
175
176 m_previous = std::exchange(m_current, read_hash_state(m_hash));
177 } else {
178 m_unprocessed_transcript.push_back(
179 std::vector(serialized_message, serialized_message + serialized_message_length));
180 }
181}
182
184 BOTAN_STATE_CHECK(!m_current.empty());
185 return m_current;
186}
187
189 BOTAN_STATE_CHECK(!m_previous.empty());
190 return m_previous;
191}
192
194 BOTAN_STATE_CHECK(!m_truncated.empty());
195 return m_truncated;
196}
197
198void Transcript_Hash_State::set_algorithm(std::string_view algo_spec) {
199 BOTAN_STATE_CHECK(m_hash == nullptr || m_hash->name() == algo_spec);
200 if(m_hash != nullptr) {
201 return;
202 }
203
204 m_hash = HashFunction::create_or_throw(algo_spec);
205 for(const auto& msg : m_unprocessed_transcript) {
206 update(msg);
207 }
208 m_unprocessed_transcript.clear();
209}
210
214
215} // namespace Botan::TLS
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:49
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:308
void update(std::span< const uint8_t > serialized_message_s)
const Transcript_Hash & current() 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
Transcript_Hash_State & operator=(const Transcript_Hash_State &)=delete
std::vector< uint8_t > Transcript_Hash
Definition tls_magic.h:93