Botan 3.12.0
Crypto and TLS for C&
tls_extensions_psk.cpp
Go to the documentation of this file.
1/*
2* TLS Extension Pre Shared Key
3* (C) 2022-2023 Jack Lloyd
4* 2022 René Meusel - neXenio GmbH
5* 2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
6*
7* Botan is released under the Simplified BSD License (see license.txt)
8*/
9
10#include <botan/tls_extensions_13.h>
11
12#include <botan/credentials_manager.h>
13#include <botan/hash.h>
14#include <botan/tls_callbacks.h>
15#include <botan/tls_exceptn.h>
16#include <botan/tls_psk_identity_13.h>
17#include <botan/tls_session.h>
18#include <botan/tls_session_manager.h>
19#include <botan/internal/ct_utils.h>
20#include <botan/internal/stl_util.h>
21#include <botan/internal/tls_cipher_state.h>
22#include <botan/internal/tls_reader.h>
23#include <algorithm>
24#include <utility>
25
26namespace Botan::TLS {
27
28namespace {
29
30decltype(auto) calculate_age(std::chrono::system_clock::time_point then, std::chrono::system_clock::time_point now) {
31 // TODO: Currently this does not provide actual millisecond resolution.
32 // This might become a problem when "early data" is implemented and we
33 // deal with servers that employ a strict "freshness" criteria on the
34 // ticket's age.
35 return std::chrono::duration_cast<std::chrono::milliseconds>(now - then);
36}
37
38class Client_PSK {
39 public:
40 Client_PSK(Session_with_Handle& session_to_resume, std::chrono::system_clock::time_point timestamp) :
41 Client_PSK(PskIdentity(session_to_resume.handle.opaque_handle(),
42 calculate_age(session_to_resume.session.start_time(), timestamp),
43 session_to_resume.session.session_age_add()),
44 session_to_resume.session.ciphersuite().prf_algo(),
45 session_to_resume.session.extract_master_secret(),
46 Cipher_State::PSK_Type::Resumption) {}
47
48 // NOLINTNEXTLINE(*-rvalue-reference-param-not-moved)
49 explicit Client_PSK(ExternalPSK&& psk) :
50 Client_PSK(PskIdentity(PresharedKeyID(psk.identity())),
51 psk.prf_algo(),
52 psk.extract_master_secret(),
53 psk.is_imported() ? Cipher_State::PSK_Type::Imported : Cipher_State::PSK_Type::External) {}
54
55 Client_PSK(PskIdentity id, std::vector<uint8_t> bndr) :
56 m_identity(std::move(id)), m_binder(std::move(bndr)), m_is_resumption(false) {}
57
58 Client_PSK(PskIdentity id,
59 std::string_view prf_algo,
60 secure_vector<uint8_t>&& master_secret,
61 Cipher_State::PSK_Type psk_type) :
62 m_identity(std::move(id)),
63
64 // RFC 8446 4.2.11.2
65 // Each entry in the binders list is computed as an HMAC over a transcript
66 // hash (see Section 4.4.1) containing a partial ClientHello up to and
67 // including the PreSharedKeyExtension.identities field. That is, it
68 // includes all of the ClientHello but not the binders list itself. The
69 // length fields for the message (including the overall length, the length
70 // of the extensions block, and the length of the "pre_shared_key"
71 // extension) are all set as if binders of the correct lengths were
72 // present.
73 //
74 // Hence, we fill the binders with dummy values of the correct length and use
75 // `Client_Hello_13::truncate()` to split them off before calculating the
76 // transcript hash that underpins the PSK binders. S.a. `calculate_binders()`
77 m_binder(HashFunction::create_or_throw(prf_algo)->output_length()),
78 m_is_resumption(psk_type == Cipher_State::PSK_Type::Resumption),
79 m_cipher_state(
80 Cipher_State::init_with_psk(Connection_Side::Client, psk_type, std::move(master_secret), prf_algo)) {}
81
82 const PskIdentity& identity() const { return m_identity; }
83
84 const std::vector<uint8_t>& binder() const { return m_binder; }
85
86 bool is_resumption() const { return m_is_resumption; }
87
88 void set_binder(std::vector<uint8_t> binder) { m_binder = std::move(binder); }
89
90 const Cipher_State& cipher_state() const {
91 BOTAN_ASSERT_NONNULL(m_cipher_state);
92 return *m_cipher_state;
93 }
94
95 std::unique_ptr<Cipher_State> take_cipher_state() { return std::exchange(m_cipher_state, nullptr); }
96
97 private:
98 PskIdentity m_identity;
99 std::vector<uint8_t> m_binder;
100 bool m_is_resumption;
101
102 // Clients set up associated cipher states for PSKs
103 // Servers leave this as nullptr
104 std::unique_ptr<Cipher_State> m_cipher_state;
105};
106
107class Server_PSK {
108 public:
109 explicit Server_PSK(uint16_t id) : m_selected_identity(id), m_session_to_resume_or_psk(std::monostate()) {}
110
111 Server_PSK(uint16_t id, Session session) :
112 m_selected_identity(id), m_session_to_resume_or_psk(std::move(session)) {}
113
114 Server_PSK(uint16_t id, ExternalPSK psk) : m_selected_identity(id), m_session_to_resume_or_psk(std::move(psk)) {}
115
116 uint16_t selected_identity() const { return m_selected_identity; }
117
118 std::variant<std::monostate, Session, ExternalPSK> take_session_to_resume_or_psk() {
119 BOTAN_STATE_CHECK(!std::holds_alternative<std::monostate>(m_session_to_resume_or_psk));
120 return std::exchange(m_session_to_resume_or_psk, std::monostate());
121 }
122
123 private:
124 uint16_t m_selected_identity;
125
126 // Servers store the Session to resume or the PSK of their selection
127 std::variant<std::monostate, Session, ExternalPSK> m_session_to_resume_or_psk;
128};
129
130} // namespace
131
132class PSK::PSK_Internal {
133 public:
134 explicit PSK_Internal(Server_PSK srv_psk) : psk(std::move(srv_psk)) {}
135
136 explicit PSK_Internal(std::vector<Client_PSK> clt_psks) : psk(std::move(clt_psks)) {}
137
138 // NOLINTNEXTLINE(*-non-private-member-variable*)
139 std::variant<std::vector<Client_PSK>, Server_PSK> psk;
140};
141
142PSK::PSK(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type message_type) {
143 if(message_type == Handshake_Type::ServerHello) {
144 if(extension_size != 2) {
145 throw TLS_Exception(Alert::DecodeError, "Server provided a malformed PSK extension");
146 }
147
148 const uint16_t selected_id = reader.get_uint16_t();
149 m_impl = std::make_unique<PSK_Internal>(Server_PSK(selected_id));
150 } else if(message_type == Handshake_Type::ClientHello) {
151 const auto identities_length = reader.get_uint16_t();
152 const auto identities_offset = reader.read_so_far();
153
154 std::vector<PskIdentity> psk_identities;
155 while(reader.has_remaining() && (reader.read_so_far() - identities_offset) < identities_length) {
156 /* Per RFC 8446 PskIdentity is
157
158 struct {
159 opaque identity<1..2^16-1>;
160 uint32 obfuscated_ticket_age;
161 } PskIdentity;
162
163 so we should reject an empty identity. However BoGo seems to expect
164 being able to send us such an identity, so for now we accept it.
165 */
166
167 auto identity = reader.get_tls_length_value(2);
168 const auto obfuscated_ticket_age = reader.get_uint32_t();
169 psk_identities.emplace_back(std::move(identity), obfuscated_ticket_age);
170 }
171
172 if(psk_identities.empty()) {
173 throw TLS_Exception(Alert::DecodeError, "Empty PSK list");
174 }
175
176 if(reader.read_so_far() - identities_offset != identities_length) {
177 throw TLS_Exception(Alert::DecodeError, "Inconsistent PSK identity list");
178 }
179
180 const auto binders_length = reader.get_uint16_t();
181 const auto binders_offset = reader.read_so_far();
182
183 if(binders_length == 0) {
184 throw TLS_Exception(Alert::DecodeError, "Empty PSK binders list");
185 }
186
187 std::vector<Client_PSK> psks;
188 for(auto& psk_identity : psk_identities) {
189 if(!reader.has_remaining() || reader.read_so_far() - binders_offset >= binders_length) {
190 throw TLS_Exception(Alert::IllegalParameter, "Not enough PSK binders");
191 }
192
193 // RFC 8446 4.2.11 declares PskBinderEntry opaque<32..255>, but we accept any
194 // 0..255 length here and let validate_binder reject, which yields a bad_record_mac
195 // alert rather than decode_error. BoringSSL behaves the same way and BoGo has
196 // tests that specifically expect this.
197
198 psks.emplace_back(std::move(psk_identity), reader.get_tls_length_value(1));
199 }
200
201 if(reader.read_so_far() - binders_offset != binders_length) {
202 throw TLS_Exception(Alert::IllegalParameter, "Too many PSK binders");
203 }
204
205 m_impl = std::make_unique<PSK_Internal>(std::move(psks));
206 } else {
207 throw TLS_Exception(Alert::DecodeError, "Found a PSK extension in an unexpected handshake message");
208 }
209}
210
211PSK::PSK(std::optional<Session_with_Handle>& session_to_resume, std::vector<ExternalPSK> psks, Callbacks& callbacks) {
212 std::vector<Client_PSK> cpsk;
213
214 if(session_to_resume.has_value()) {
215 cpsk.emplace_back(session_to_resume.value(), callbacks.tls_current_timestamp());
216 }
217
218 for(auto&& psk : psks) {
219 cpsk.emplace_back(std::move(psk));
220 }
221
222 m_impl = std::make_unique<PSK_Internal>(std::move(cpsk));
223}
224
225PSK::PSK(Session session_to_resume, uint16_t psk_index) :
226 m_impl(std::make_unique<PSK_Internal>(Server_PSK(psk_index, std::move(session_to_resume)))) {}
227
228PSK::PSK(ExternalPSK psk, uint16_t psk_index) :
229 m_impl(std::make_unique<PSK_Internal>(Server_PSK(psk_index, std::move(psk)))) {}
230
231PSK::~PSK() = default;
232
233bool PSK::empty() const {
234 if(std::holds_alternative<Server_PSK>(m_impl->psk)) {
235 return false;
236 }
237
238 BOTAN_ASSERT_NOMSG(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
239 return std::get<std::vector<Client_PSK>>(m_impl->psk).empty();
240}
241
242std::pair<std::optional<std::string>, std::unique_ptr<Cipher_State>> PSK::take_selected_psk_info(
243 const PSK& server_psk, const Ciphersuite& cipher) {
244 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
245 BOTAN_STATE_CHECK(std::holds_alternative<Server_PSK>(server_psk.m_impl->psk));
246
247 const auto id = std::get<Server_PSK>(server_psk.m_impl->psk).selected_identity();
248 auto& ids = std::get<std::vector<Client_PSK>>(m_impl->psk);
249
250 // RFC 8446 4.2.11
251 // Clients MUST verify that the server's selected_identity is within the
252 // range supplied by the client, [...]. If these values are not
253 // consistent, the client MUST abort the handshake with an
254 // "illegal_parameter" alert.
255 if(id >= ids.size()) {
256 throw TLS_Exception(Alert::IllegalParameter, "PSK identity selected by server is out of bounds");
257 }
258
259 auto& selected_psk = ids.at(id);
260 auto cipher_state = selected_psk.take_cipher_state();
261
262 BOTAN_ASSERT_NONNULL(cipher_state);
263
264 auto psk_id = [&]() -> std::optional<std::string> {
265 if(selected_psk.is_resumption()) {
266 return std::nullopt;
267 }
268 return selected_psk.identity().identity_as_string();
269 }();
270
271 // destroy cipher states and PSKs that were not selected by the server
272 ids.clear();
273
274 // RFC 8446 4.2.11
275 // Clients MUST verify that [...] the server selected a cipher suite
276 // indicating a Hash associated with the PSK [...]. If these values
277 // are not consistent, the client MUST abort the handshake with an
278 // "illegal_parameter" alert.
279 if(!cipher_state->is_compatible_with(cipher)) {
280 throw TLS_Exception(Alert::IllegalParameter, "PSK and ciphersuite selected by server are not compatible");
281 }
282
283 return {std::move(psk_id), std::move(cipher_state)};
284}
285
286std::unique_ptr<PSK> PSK::select_offered_psk(std::string_view host,
287 const Ciphersuite& cipher,
288 Session_Manager& session_mgr,
289 Credentials_Manager& credentials_mgr,
290 Callbacks& callbacks,
291 const Policy& policy) {
292 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
293
294 auto& psks = std::get<std::vector<Client_PSK>>(m_impl->psk);
295
296 //
297 // PSK for Session Resumption
298 //
299 std::vector<PskIdentity> psk_identities;
300 std::transform(
301 psks.begin(), psks.end(), std::back_inserter(psk_identities), [&](const auto& psk) { return psk.identity(); });
302 if(auto selected_session =
303 session_mgr.choose_from_offered_tickets(psk_identities, cipher.prf_algo(), callbacks, policy)) {
304 auto& [session, psk_index] = selected_session.value();
305
306 // Refuse to resume a ticket across SNI: a session minted for one
307 // virtual host must not be presentable against another. Treat as a
308 // cache miss and fall through to the external PSK path rather than
309 // failing the connection.
310 if(session.server_info().hostname() == host) {
311 // RFC 8446 4.6.1
312 // Any ticket MUST only be resumed with a cipher suite that has the
313 // same KDF hash algorithm as that used to establish the original
314 // connection.
315 if(session.ciphersuite().prf_algo() != cipher.prf_algo()) {
316 throw TLS_Exception(Alert::InternalError,
317 "Application chose a ticket that is not compatible with the negotiated ciphersuite");
318 }
319
320 return std::unique_ptr<PSK>(new PSK(std::move(session), psk_index));
321 }
322 }
323
324 //
325 // Externally provided PSKs
326 //
327 std::vector<std::string> psk_ids;
328 std::transform(psks.begin(), psks.end(), std::back_inserter(psk_ids), [&](const auto& psk) {
329 return psk.identity().identity_as_string();
330 });
331 if(auto selected_psk =
332 credentials_mgr.choose_preshared_key(host, Connection_Side::Server, psk_ids, cipher.prf_algo())) {
333 auto& psk = selected_psk.value();
334
335 // RFC 8446 4.2.11
336 // Each PSK is associated with a single Hash algorithm. [...]
337 // The server MUST ensure that it selects a compatible PSK (if any)
338 // and cipher suite.
339 if(psk.prf_algo() != cipher.prf_algo()) {
340 throw TLS_Exception(Alert::InternalError,
341 "Application chose a PSK that is not compatible with the negotiated ciphersuite");
342 }
343
344 const auto selected_itr =
345 std::find_if(psk_identities.begin(), psk_identities.end(), [&](const auto& offered_psk) {
346 return offered_psk.identity_as_string() == psk.identity();
347 });
348 if(selected_itr == psk_identities.end()) {
349 throw TLS_Exception(Alert::InternalError,
350 "Application provided a PSK with an identity that was not offered by the client");
351 }
352
353 // When implementing a server that works with PSKs exclusively, we might
354 // want to take TLS::Policy::hide_unknown_users() into account and
355 // obfuscate malicious "identity guesses" by generating a random PSK and
356 // letting the handshake continue.
357 //
358 // Currently, we do not have a notion of "a server that solely supports
359 // PSK handshakes". However, such applications are free to implement such
360 // a mitigation in their override of Credentials_Manager::choose_preshared_key().
361 //
362 // TODO: Take RFC 8446 Appendix E.6 into account, especially when
363 // implementing PSK_KE (aka. PSK-only mode).
364 return std::unique_ptr<PSK>(
365 new PSK(std::move(psk), static_cast<uint16_t>(std::distance(psk_identities.begin(), selected_itr))));
366 }
367
368 return nullptr;
369}
370
371void PSK::filter(const Ciphersuite& cipher) {
372 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
373 auto& psks = std::get<std::vector<Client_PSK>>(m_impl->psk);
374
375 const auto r = std::remove_if(psks.begin(), psks.end(), [&](const auto& psk) {
376 const auto& cipher_state = psk.cipher_state();
377 return !cipher_state.is_compatible_with(cipher);
378 });
379 psks.erase(r, psks.end());
380}
381
382std::variant<Session, ExternalPSK> PSK::take_session_to_resume_or_psk() {
383 BOTAN_STATE_CHECK(std::holds_alternative<Server_PSK>(m_impl->psk));
384
385 return std::visit(
386 [](auto out) -> std::variant<Session, ExternalPSK> {
387 if constexpr(std::is_same_v<decltype(out), std::monostate>) {
388 throw TLS_Exception(AlertType::InternalError, "cannot extract an empty PSK");
389 } else {
390 return out;
391 }
392 },
393 std::get<Server_PSK>(m_impl->psk).take_session_to_resume_or_psk());
394}
395
396std::vector<uint8_t> PSK::serialize(Connection_Side side) const {
397 std::vector<uint8_t> result;
398
399 std::visit(overloaded{
400 [&](const Server_PSK& psk) {
402 result.reserve(2);
403 const uint16_t id = psk.selected_identity();
404 result.push_back(get_byte<0>(id));
405 result.push_back(get_byte<1>(id));
406 },
407 [&](const std::vector<Client_PSK>& psks) {
409
410 std::vector<uint8_t> identities;
411 std::vector<uint8_t> binders;
412 for(const auto& psk : psks) {
413 const auto& psk_identity = psk.identity();
414 append_tls_length_value(identities, psk_identity.identity(), 2);
415
416 const uint32_t obfuscated_ticket_age = psk_identity.obfuscated_age();
417 identities.push_back(get_byte<0>(obfuscated_ticket_age));
418 identities.push_back(get_byte<1>(obfuscated_ticket_age));
419 identities.push_back(get_byte<2>(obfuscated_ticket_age));
420 identities.push_back(get_byte<3>(obfuscated_ticket_age));
421
422 append_tls_length_value(binders, psk.binder(), 1);
423 }
424
425 append_tls_length_value(result, identities, 2);
426 append_tls_length_value(result, binders, 2);
427 },
428 },
429 m_impl->psk);
430
431 return result;
432}
433
434// See RFC 8446 4.2.11.2 for details on how these binders are calculated
435void PSK::calculate_binders(const Transcript_Hash_State& truncated_transcript_hash) {
436 BOTAN_ASSERT_NOMSG(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
437 for(auto& psk : std::get<std::vector<Client_PSK>>(m_impl->psk)) {
438 auto tth = truncated_transcript_hash.clone();
439 const auto& cipher_state = psk.cipher_state();
440 tth.set_algorithm(cipher_state.hash_algorithm());
441 psk.set_binder(cipher_state.psk_binder_mac(tth.truncated()));
442 }
443}
444
445bool PSK::validate_binder(const PSK& server_psk, const std::vector<uint8_t>& binder) const {
446 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
447 BOTAN_STATE_CHECK(std::holds_alternative<Server_PSK>(server_psk.m_impl->psk));
448
449 const uint16_t index = std::get<Server_PSK>(server_psk.m_impl->psk).selected_identity();
450 const auto& psks = std::get<std::vector<Client_PSK>>(m_impl->psk);
451
452 BOTAN_STATE_CHECK(index < psks.size());
453 const auto& expected_binder = psks[index].binder();
454 return CT::is_equal<uint8_t>(binder, expected_binder).as_bool();
455}
456
457} // namespace Botan::TLS
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:49
#define BOTAN_ASSERT_NONNULL(ptr)
Definition assert.h:114
virtual std::optional< TLS::ExternalPSK > choose_preshared_key(std::string_view host, TLS::Connection_Side whoami, const std::vector< std::string > &identities, const std::optional< std::string > &prf=std::nullopt)
virtual std::chrono::system_clock::time_point tls_current_timestamp()
std::string prf_algo() const
std::pair< std::optional< std::string >, std::unique_ptr< Cipher_State > > take_selected_psk_info(const PSK &server_psk, const Ciphersuite &cipher)
std::variant< Session, ExternalPSK > take_session_to_resume_or_psk()
void filter(const Ciphersuite &cipher)
std::vector< uint8_t > serialize(Connection_Side side) const override
bool validate_binder(const PSK &server_psk, const std::vector< uint8_t > &binder) const
void calculate_binders(const Transcript_Hash_State &truncated_transcript_hash)
std::unique_ptr< PSK > select_offered_psk(std::string_view host, const Ciphersuite &cipher, Session_Manager &session_mgr, Credentials_Manager &credentials_mgr, Callbacks &callbacks, const Policy &policy)
~PSK() override
bool empty() const override
PSK(TLS_Data_Reader &reader, uint16_t extension_size, Handshake_Type message_type)
virtual std::optional< std::pair< Session, uint16_t > > choose_from_offered_tickets(const std::vector< PskIdentity > &tickets, std::string_view hash_function, Callbacks &callbacks, const Policy &policy)
size_t read_so_far() const
Definition tls_reader.h:35
std::vector< uint8_t > get_tls_length_value(size_t len_bytes)
Definition tls_reader.h:105
void set_algorithm(std::string_view algo_spec)
constexpr CT::Mask< T > is_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:798
Strong< std::string, struct PresharedKeyID_ > PresharedKeyID
holds a PSK identity as used in TLS 1.3
Definition tls_psk_13.h:28
void append_tls_length_value(std::vector< uint8_t, Alloc > &buf, const T *vals, size_t vals_size, size_t tag_size)
Definition tls_reader.h:177
constexpr uint8_t get_byte(T input)
Definition loadstor.h:79
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:68