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

#include <tls_extensions.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 661 of file tls_extensions.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 auto identity = reader.get_tls_length_value(2);
157 const auto obfuscated_ticket_age = reader.get_uint32_t();
158 psk_identities.emplace_back(std::move(identity), obfuscated_ticket_age);
159 }
160
161 if(psk_identities.empty()) {
162 throw TLS_Exception(Alert::DecodeError, "Empty PSK list");
163 }
164
165 if(reader.read_so_far() - identities_offset != identities_length) {
166 throw TLS_Exception(Alert::DecodeError, "Inconsistent PSK identity list");
167 }
168
169 const auto binders_length = reader.get_uint16_t();
170 const auto binders_offset = reader.read_so_far();
171
172 if(binders_length == 0) {
173 throw TLS_Exception(Alert::DecodeError, "Empty PSK binders list");
174 }
175
176 std::vector<Client_PSK> psks;
177 for(auto& psk_identity : psk_identities) {
178 if(!reader.has_remaining() || reader.read_so_far() - binders_offset >= binders_length) {
179 throw TLS_Exception(Alert::IllegalParameter, "Not enough PSK binders");
180 }
181
182 psks.emplace_back(std::move(psk_identity), reader.get_tls_length_value(1));
183 }
184
185 if(reader.read_so_far() - binders_offset != binders_length) {
186 throw TLS_Exception(Alert::IllegalParameter, "Too many PSK binders");
187 }
188
189 m_impl = std::make_unique<PSK_Internal>(std::move(psks));
190 } else {
191 throw TLS_Exception(Alert::DecodeError, "Found a PSK extension in an unexpected handshake message");
192 }
193}

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.

◆ 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 195 of file tls_extensions_psk.cpp.

195 {
196 std::vector<Client_PSK> cpsk;
197
198 if(session_to_resume.has_value()) {
199 cpsk.emplace_back(session_to_resume.value(), callbacks.tls_current_timestamp());
200 }
201
202 for(auto&& psk : psks) {
203 cpsk.emplace_back(std::move(psk));
204 }
205
206 m_impl = std::make_unique<PSK_Internal>(std::move(cpsk));
207}

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 413 of file tls_extensions_psk.cpp.

413 {
414 BOTAN_ASSERT_NOMSG(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
415 for(auto& psk : std::get<std::vector<Client_PSK>>(m_impl->psk)) {
416 auto tth = truncated_transcript_hash.clone();
417 const auto& cipher_state = psk.cipher_state();
418 tth.set_algorithm(cipher_state.hash_algorithm());
419 psk.set_binder(cipher_state.psk_binder_mac(tth.truncated()));
420 }
421}
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:59

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

◆ empty()

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

Implements Botan::TLS::Extension.

Definition at line 217 of file tls_extensions_psk.cpp.

217 {
218 if(std::holds_alternative<Server_PSK>(m_impl->psk)) {
219 return false;
220 }
221
222 BOTAN_ASSERT_NOMSG(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
223 return std::get<std::vector<Client_PSK>>(m_impl->psk).empty();
224}
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 349 of file tls_extensions_psk.cpp.

349 {
350 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
351 auto& psks = std::get<std::vector<Client_PSK>>(m_impl->psk);
352
353 const auto r = std::remove_if(psks.begin(), psks.end(), [&](const auto& psk) {
354 const auto& cipher_state = psk.cipher_state();
355 return !cipher_state.is_compatible_with(cipher);
356 });
357 psks.erase(r, psks.end());
358}
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:41

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 116 of file tls_extensions.h.

116{ 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 270 of file tls_extensions_psk.cpp.

275 {
276 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
277
278 auto& psks = std::get<std::vector<Client_PSK>>(m_impl->psk);
279
280 //
281 // PSK for Session Resumption
282 //
283 std::vector<PskIdentity> psk_identities;
284 std::transform(
285 psks.begin(), psks.end(), std::back_inserter(psk_identities), [&](const auto& psk) { return psk.identity(); });
286 if(auto selected_session =
287 session_mgr.choose_from_offered_tickets(psk_identities, cipher.prf_algo(), callbacks, policy)) {
288 auto& [session, psk_index] = selected_session.value();
289
290 // RFC 8446 4.6.1
291 // Any ticket MUST only be resumed with a cipher suite that has the
292 // same KDF hash algorithm as that used to establish the original
293 // connection.
294 if(session.ciphersuite().prf_algo() != cipher.prf_algo()) {
295 throw TLS_Exception(Alert::InternalError,
296 "Application chose a ticket that is not compatible with the negotiated ciphersuite");
297 }
298
299 return std::unique_ptr<PSK>(new PSK(std::move(session), psk_index));
300 }
301
302 //
303 // Externally provided PSKs
304 //
305 std::vector<std::string> psk_ids;
306 std::transform(psks.begin(), psks.end(), std::back_inserter(psk_ids), [&](const auto& psk) {
307 return psk.identity().identity_as_string();
308 });
309 if(auto selected_psk =
310 credentials_mgr.choose_preshared_key(host, Connection_Side::Server, psk_ids, cipher.prf_algo())) {
311 auto& psk = selected_psk.value();
312
313 // RFC 8446 4.2.11
314 // Each PSK is associated with a single Hash algorithm. [...]
315 // The server MUST ensure that it selects a compatible PSK (if any)
316 // and cipher suite.
317 if(psk.prf_algo() != cipher.prf_algo()) {
318 throw TLS_Exception(Alert::InternalError,
319 "Application chose a PSK that is not compatible with the negotiated ciphersuite");
320 }
321
322 const auto selected_itr =
323 std::find_if(psk_identities.begin(), psk_identities.end(), [&](const auto& offered_psk) {
324 return offered_psk.identity_as_string() == psk.identity();
325 });
326 if(selected_itr == psk_identities.end()) {
327 throw TLS_Exception(Alert::InternalError,
328 "Application provided a PSK with an identity that was not offered by the client");
329 }
330
331 // When implementing a server that works with PSKs exclusively, we might
332 // want to take TLS::Policy::hide_unknown_users() into account and
333 // obfuscate malicious "identity guesses" by generating a random PSK and
334 // letting the handshake continue.
335 //
336 // Currently, we do not have a notion of "a server that solely supports
337 // PSK handshakes". However, such applications are free to implement such
338 // a mitigation in their override of Credentials_Manager::choose_preshared_key().
339 //
340 // TODO: Take RFC 8446 Appendix E.6 into account, especially when
341 // implementing PSK_KE (aka. PSK-only mode).
342 return std::unique_ptr<PSK>(
343 new PSK(std::move(psk), static_cast<uint16_t>(std::distance(psk_identities.begin(), selected_itr))));
344 }
345
346 return nullptr;
347}

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

Referenced by Botan::TLS::Server_Hello_13::Server_Hello_13().

◆ 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 374 of file tls_extensions_psk.cpp.

374 {
375 std::vector<uint8_t> result;
376
377 std::visit(overloaded{
378 [&](const Server_PSK& psk) {
380 result.reserve(2);
381 const uint16_t id = psk.selected_identity();
382 result.push_back(get_byte<0>(id));
383 result.push_back(get_byte<1>(id));
384 },
385 [&](const std::vector<Client_PSK>& psks) {
387
388 std::vector<uint8_t> identities;
389 std::vector<uint8_t> binders;
390 for(const auto& psk : psks) {
391 const auto& psk_identity = psk.identity();
392 append_tls_length_value(identities, psk_identity.identity(), 2);
393
394 const uint32_t obfuscated_ticket_age = psk_identity.obfuscated_age();
395 identities.push_back(get_byte<0>(obfuscated_ticket_age));
396 identities.push_back(get_byte<1>(obfuscated_ticket_age));
397 identities.push_back(get_byte<2>(obfuscated_ticket_age));
398 identities.push_back(get_byte<3>(obfuscated_ticket_age));
399
400 append_tls_length_value(binders, psk.binder(), 1);
401 }
402
403 append_tls_length_value(result, identities, 2);
404 append_tls_length_value(result, binders, 2);
405 },
406 },
407 m_impl->psk);
408
409 return result;
410}
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:180
constexpr uint8_t get_byte(T input)
Definition loadstor.h:75
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()

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

Definition at line 663 of file tls_extensions.h.

◆ 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 authorative value for the user. We therefore report the identity of externally provided PSKs only.

Definition at line 226 of file tls_extensions_psk.cpp.

227 {
228 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
229 BOTAN_STATE_CHECK(std::holds_alternative<Server_PSK>(server_psk.m_impl->psk));
230
231 const auto id = std::get<Server_PSK>(server_psk.m_impl->psk).selected_identity();
232 auto& ids = std::get<std::vector<Client_PSK>>(m_impl->psk);
233
234 // RFC 8446 4.2.11
235 // Clients MUST verify that the server's selected_identity is within the
236 // range supplied by the client, [...]. If these values are not
237 // consistent, the client MUST abort the handshake with an
238 // "illegal_parameter" alert.
239 if(id >= ids.size()) {
240 throw TLS_Exception(Alert::IllegalParameter, "PSK identity selected by server is out of bounds");
241 }
242
243 auto& selected_psk = ids.at(id);
244 auto cipher_state = selected_psk.take_cipher_state();
245
246 BOTAN_ASSERT_NONNULL(cipher_state);
247
248 auto psk_id = [&]() -> std::optional<std::string> {
249 if(selected_psk.is_resumption()) {
250 return std::nullopt;
251 }
252 return selected_psk.identity().identity_as_string();
253 }();
254
255 // destroy cipher states and PSKs that were not selected by the server
256 ids.clear();
257
258 // RFC 8446 4.2.11
259 // Clients MUST verify that [...] the server selected a cipher suite
260 // indicating a Hash associated with the PSK [...]. If these values
261 // are not consistent, the client MUST abort the handshake with an
262 // "illegal_parameter" alert.
263 if(!cipher_state->is_compatible_with(cipher)) {
264 throw TLS_Exception(Alert::IllegalParameter, "PSK and ciphersuite selected by server are not compatible");
265 }
266
267 return {std::move(psk_id), std::move(cipher_state)};
268}
#define BOTAN_ASSERT_NONNULL(ptr)
Definition assert.h:86

References BOTAN_ASSERT_NONNULL, and BOTAN_STATE_CHECK.

◆ 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 360 of file tls_extensions_psk.cpp.

360 {
361 BOTAN_STATE_CHECK(std::holds_alternative<Server_PSK>(m_impl->psk));
362
363 return std::visit(
364 [](auto out) -> std::variant<Session, ExternalPSK> {
365 if constexpr(std::is_same_v<decltype(out), std::monostate>) {
366 throw TLS_Exception(AlertType::InternalError, "cannot extract an empty PSK");
367 } else {
368 return out;
369 }
370 },
371 std::get<Server_PSK>(m_impl->psk).take_session_to_resume_or_psk());
372}

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 665 of file tls_extensions.h.

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

◆ validate_binder()

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

Definition at line 423 of file tls_extensions_psk.cpp.

423 {
424 BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
425 BOTAN_STATE_CHECK(std::holds_alternative<Server_PSK>(server_psk.m_impl->psk));
426
427 const uint16_t index = std::get<Server_PSK>(server_psk.m_impl->psk).selected_identity();
428 const auto& psks = std::get<std::vector<Client_PSK>>(m_impl->psk);
429
430 BOTAN_STATE_CHECK(index < psks.size());
431 return psks[index].binder() == binder;
432}

References BOTAN_STATE_CHECK.


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