Botan 3.12.0
Crypto and TLS for C&
Botan::TLS::PSK Class Referencefinal

#include <tls_extensions_13.h>

Inheritance diagram for Botan::TLS::PSK:
Botan::TLS::Extension

Public Member Functions

void calculate_binders (const Transcript_Hash_State &truncated_transcript_hash)
bool empty () const override
void filter (const Ciphersuite &cipher)
virtual bool is_implemented () const
 PSK (std::optional< Session_with_Handle > &session_to_resume, std::vector< ExternalPSK > psks, Callbacks &callbacks)
 PSK (TLS_Data_Reader &reader, uint16_t extension_size, Handshake_Type message_type)
std::unique_ptr< PSKselect_offered_psk (std::string_view host, const Ciphersuite &cipher, Session_Manager &session_mgr, Credentials_Manager &credentials_mgr, Callbacks &callbacks, const Policy &policy)
std::vector< uint8_t > serialize (Connection_Side side) const override
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, ExternalPSKtake_session_to_resume_or_psk ()
Extension_Code type () const override
bool validate_binder (const PSK &server_psk, const std::vector< uint8_t > &binder) const
 ~PSK () override

Static Public Member Functions

static Extension_Code static_type ()

Detailed Description

Pre-Shared Key extension from RFC 8446 4.2.11

Definition at line 110 of file tls_extensions_13.h.

Constructor & Destructor Documentation

◆ PSK() [1/2]

Botan::TLS::PSK::PSK ( TLS_Data_Reader & reader,
uint16_t extension_size,
Handshake_Type message_type )

Definition at line 142 of file tls_extensions_psk.cpp.

142 {
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}

References Botan::TLS::ClientHello, Botan::TLS::TLS_Data_Reader::get_tls_length_value(), Botan::TLS::TLS_Data_Reader::get_uint16_t(), Botan::TLS::TLS_Data_Reader::get_uint32_t(), Botan::TLS::TLS_Data_Reader::has_remaining(), Botan::TLS::TLS_Data_Reader::read_so_far(), and Botan::TLS::ServerHello.

Referenced by select_offered_psk(), take_selected_psk_info(), validate_binder(), and ~PSK().

◆ PSK() [2/2]

Botan::TLS::PSK::PSK ( std::optional< Session_with_Handle > & session_to_resume,
std::vector< ExternalPSK > psks,
Callbacks & callbacks )

Creates a PSK extension with a TLS 1.3 session object containing a master_secret. Note that it will extract that secret from the session, and won't create a copy of it.

Parameters
session_to_resumethe session to be resumed; note that the master secret will be taken away from the session object.
psksa list of non-resumption PSKs that should be offered to the server
callbacksthe application's callbacks

Definition at line 211 of file tls_extensions_psk.cpp.

211 {
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}

References Botan::TLS::Callbacks::tls_current_timestamp().

◆ ~PSK()

Botan::TLS::PSK::~PSK ( )
overridedefault

Member Function Documentation

◆ calculate_binders()

void Botan::TLS::PSK::calculate_binders ( const Transcript_Hash_State & truncated_transcript_hash)

Definition at line 435 of file tls_extensions_psk.cpp.

435 {
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}
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75

References BOTAN_ASSERT_NOMSG, Botan::TLS::Transcript_Hash_State::clone(), and Botan::TLS::Transcript_Hash_State::set_algorithm().

Referenced by ~PSK().

◆ empty()

bool Botan::TLS::PSK::empty ( ) const
overridevirtual
Returns
if we should encode this extension or not

Implements Botan::TLS::Extension.

Definition at line 233 of file tls_extensions_psk.cpp.

233 {
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}
bool empty() const override

References BOTAN_ASSERT_NOMSG, and empty().

Referenced by empty().

◆ filter()

void Botan::TLS::PSK::filter ( const Ciphersuite & cipher)

Remove PSK identities from the list in m_psk that are not compatible with the passed in cipher suite. This is useful to react to Hello Retry Requests. See RFC 8446 4.1.4.

Definition at line 371 of file tls_extensions_psk.cpp.

371 {
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}
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:49

References BOTAN_STATE_CHECK.

◆ is_implemented()

virtual bool Botan::TLS::Extension::is_implemented ( ) const
inlinevirtualinherited
Returns
true if this extension is known and implemented by Botan

Reimplemented in Botan::TLS::Unknown_Extension.

Definition at line 100 of file tls_extensions.h.

100{ return true; }

◆ select_offered_psk()

std::unique_ptr< PSK > Botan::TLS::PSK::select_offered_psk ( std::string_view host,
const Ciphersuite & cipher,
Session_Manager & session_mgr,
Credentials_Manager & credentials_mgr,
Callbacks & callbacks,
const Policy & policy )

Selects one of the offered PSKs that is compatible with cipher.

Return values
PSKextension object that can be added to the Server Hello response
std::nullptrif no PSK offered by the client is convenient

Definition at line 286 of file tls_extensions_psk.cpp.

291 {
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}
PSK(TLS_Data_Reader &reader, uint16_t extension_size, Handshake_Type message_type)

References BOTAN_STATE_CHECK, Botan::TLS::Session_Manager::choose_from_offered_tickets(), Botan::Credentials_Manager::choose_preshared_key(), Botan::TLS::Ciphersuite::prf_algo(), PSK(), and Botan::TLS::Server.

◆ serialize()

std::vector< uint8_t > Botan::TLS::PSK::serialize ( Connection_Side whoami) const
overridevirtual
Returns
serialized binary for the extension

Implements Botan::TLS::Extension.

Definition at line 396 of file tls_extensions_psk.cpp.

396 {
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}
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
overloaded(Ts...) -> overloaded< Ts... >

References Botan::TLS::append_tls_length_value(), BOTAN_STATE_CHECK, Botan::TLS::Client, Botan::get_byte(), and Botan::TLS::Server.

◆ static_type()

Extension_Code Botan::TLS::PSK::static_type ( )
inlinestatic

Definition at line 112 of file tls_extensions_13.h.

References Botan::TLS::PresharedKey.

Referenced by type().

◆ take_selected_psk_info()

std::pair< std::optional< std::string >, std::unique_ptr< Cipher_State > > Botan::TLS::PSK::take_selected_psk_info ( const PSK & server_psk,
const Ciphersuite & cipher )

Returns the PSK identity (in case of an externally provided PSK) and the cipher state representing the PSK selected by the server. Note that this destructs the list of offered PSKs and its cipher states and must therefore not be called more than once.

Note
Technically, PSKs used for resumption also carry an identity. Though, typically, this is an opaque value meaningful only to the peer and of no authoritative value for the user. We therefore report the identity of externally provided PSKs only.

Definition at line 242 of file tls_extensions_psk.cpp.

243 {
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}
#define BOTAN_ASSERT_NONNULL(ptr)
Definition assert.h:114

References BOTAN_ASSERT_NONNULL, BOTAN_STATE_CHECK, and PSK().

◆ take_session_to_resume_or_psk()

std::variant< Session, ExternalPSK > Botan::TLS::PSK::take_session_to_resume_or_psk ( )

Pulls the preshared key or the Session to resume from a PSK extension in Server Hello.

Definition at line 382 of file tls_extensions_psk.cpp.

382 {
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}

References BOTAN_STATE_CHECK, and Botan::TLS::InternalError.

◆ type()

Extension_Code Botan::TLS::PSK::type ( ) const
inlineoverridevirtual
Returns
code number of the extension

Implements Botan::TLS::Extension.

Definition at line 114 of file tls_extensions_13.h.

114{ return static_type(); }
static Extension_Code static_type()

References static_type().

◆ validate_binder()

bool Botan::TLS::PSK::validate_binder ( const PSK & server_psk,
const std::vector< uint8_t > & binder ) const

Definition at line 445 of file tls_extensions_psk.cpp.

445 {
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}
constexpr CT::Mask< T > is_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:798

References BOTAN_STATE_CHECK, Botan::CT::is_equal(), and PSK().

Referenced by ~PSK().


The documentation for this class was generated from the following files: