Botan 3.4.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/tls_exceptn.h>
12#include <botan/tls_messages.h>
13#include <botan/internal/tls_reader.h>
14
15#include <utility>
16
17namespace Botan::TLS {
18
20 set_algorithm(algo_spec);
21}
22
24 m_hash((other.m_hash != nullptr) ? other.m_hash->copy_state() : nullptr),
25 m_unprocessed_transcript(other.m_unprocessed_transcript),
26 m_current(other.m_current),
27 m_previous(other.m_previous),
28 m_truncated(other.m_truncated) {}
29
31 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 BOTAN_STATE_CHECK(prev_transcript_hash_state.m_hash == nullptr);
35 BOTAN_STATE_CHECK(prev_transcript_hash_state.m_unprocessed_transcript.size() == 2);
36
37 Transcript_Hash_State ths(algo_spec);
38
39 const auto& client_hello_1 = prev_transcript_hash_state.m_unprocessed_transcript.front();
40 const auto& hello_retry_request = prev_transcript_hash_state.m_unprocessed_transcript.back();
41
42 const size_t hash_length = ths.m_hash->output_length();
43 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 std::vector<uint8_t> message_hash;
50 message_hash.reserve(4 + hash_length);
51 message_hash.push_back(0xFE /* message type 'message_hash' RFC 8446 4. */);
52 message_hash.push_back(0x00);
53 message_hash.push_back(0x00);
54 message_hash.push_back(static_cast<uint8_t>(hash_length));
55 message_hash += ths.m_hash->process(client_hello_1);
56
57 ths.update(message_hash);
58 ths.update(hello_retry_request);
59
60 return ths;
61}
62
63namespace {
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.
76size_t find_client_hello_truncation_mark(std::span<const uint8_t> client_hello) {
77 TLS_Data_Reader reader("Client Hello Truncation", client_hello);
78
79 // handshake message type
80 BOTAN_ASSERT_NOMSG(reader.get_byte() == static_cast<uint8_t>(Handshake_Type::ClientHello));
81
82 // message length
83 reader.discard_next(3);
84
85 // legacy version
86 reader.discard_next(2);
87
88 // random
89 reader.discard_next(32);
90
91 // session ID
92 const auto session_id_length = reader.get_byte();
93 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 const auto ciphersuites_length = reader.get_uint16_t();
100 reader.discard_next(ciphersuites_length);
101
102 // compression methods
103 const auto compression_methods_length = reader.get_byte();
104 reader.discard_next(compression_methods_length);
105
106 // extensions
107 const auto extensions_length = reader.get_uint16_t();
108 const auto extensions_offset = reader.read_so_far();
109 while(reader.has_remaining() && reader.read_so_far() - extensions_offset < extensions_length) {
110 const auto ext_type = static_cast<Extension_Code>(reader.get_uint16_t());
111 const auto ext_length = reader.get_uint16_t();
112
113 // skip over all extensions, finding the PSK extension to be truncated
114 if(ext_type != Extension_Code::PresharedKey) {
115 reader.discard_next(ext_length);
116 continue;
117 }
118
119 // PSK identities list
120 const auto identities_length = reader.get_uint16_t();
121 reader.discard_next(identities_length);
122
123 // check that only the binders are left in the buffer...
124 const auto binders_length = reader.peek_uint16_t();
125 if(binders_length != reader.remaining_bytes() - 2 /* binders_length */) {
126 throw TLS_Exception(Alert::IllegalParameter,
127 "Failed to truncate Client Hello that doesn't end on the PSK binders list");
128 }
129
130 // the reader now points to the truncation point
131 break;
132 }
133
134 // if no PSK extension was found, this will point to the end of the buffer
135 return reader.read_so_far();
136}
137
138std::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 return hash->copy_state()->final_stdvec();
143}
144
145} // namespace
146
147void Transcript_Hash_State::update(std::span<const uint8_t> serialized_message_s) {
148 auto serialized_message = serialized_message_s.data();
149 auto serialized_message_length = serialized_message_s.size();
150 if(m_hash != nullptr) {
151 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 if(serialized_message_length > 0 && *serialized_message == static_cast<uint8_t>(Handshake_Type::ClientHello)) {
156 truncation_mark = find_client_hello_truncation_mark(serialized_message_s);
157 }
158
159 if(truncation_mark < serialized_message_length) {
160 m_hash->update(serialized_message, truncation_mark);
161 m_truncated = read_hash_state(m_hash);
162 m_hash->update(serialized_message + truncation_mark, serialized_message_length - truncation_mark);
163 } else {
164 m_truncated.clear();
165 m_hash->update(serialized_message, serialized_message_length);
166 }
167
168 m_previous = std::exchange(m_current, read_hash_state(m_hash));
169 } else {
170 m_unprocessed_transcript.push_back(
171 std::vector(serialized_message, serialized_message + serialized_message_length));
172 }
173}
174
176 BOTAN_STATE_CHECK(!m_current.empty());
177 return m_current;
178}
179
181 BOTAN_STATE_CHECK(!m_previous.empty());
182 return m_previous;
183}
184
186 BOTAN_STATE_CHECK(!m_truncated.empty());
187 return m_truncated;
188}
189
190void Transcript_Hash_State::set_algorithm(std::string_view algo_spec) {
191 BOTAN_STATE_CHECK(m_hash == nullptr || m_hash->name() == algo_spec);
192 if(m_hash != nullptr) {
193 return;
194 }
195
196 m_hash = HashFunction::create_or_throw(algo_spec);
197 for(const auto& msg : m_unprocessed_transcript) {
198 update(msg);
199 }
200 m_unprocessed_transcript.clear();
201}
202
206
207} // namespace Botan::TLS
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:59
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:41
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:298
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
int(* update)(CTX *, const void *, CC_LONG len)
std::vector< uint8_t > Transcript_Hash
Definition tls_magic.h:81