/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 |