Repository: randombit/botan
Pull request #3617 (2023-07-06 10:21:25)

[TLS 1.3] Preparations for externally provided PSKs
The `TLS::Ticket` class was introduced in Botan 3.0.0 (#3150). Given that externally provided PSKs will use the same structure, it would be great to generalize it a bit and rename it to `TLS::PskIdentity` (similar to its name in RFC 8446). This is breaking the public API of 3.0 somewhat (we added a deprecated typedef in case someone already started relying on it). @randombit Is that acceptable?

We doubt that we'll finish the actual implementation of the PSK support before 3.1, but it might be worthwhile to consider adding this already now.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/61172016/badge)](https://coveralls.io/builds/61172016)

coverage: 91.738%. remained the same when pulling **48ce69b420a99a5fb6aa2e230abf745a127d2913 on Rohde-Schwarz:tls13/psk_preparations** into **85406718a30f713ee8768a4371a5edd26e663b28 on randombit:master**.


Patch:
diff --git a/src/lib/tls/credentials_manager.h b/src/lib/tls/credentials_manager.h
index 2591c07da..d78b58283 100644
--- a/src/lib/tls/credentials_manager.h
+++ b/src/lib/tls/credentials_manager.h
@@ -11,6 +11,7 @@
 #include <botan/asn1_obj.h>
 #include <botan/certstor.h>
 #include <botan/pk_keys.h>
+#include <botan/strong_type.h>
 #include <botan/symkey.h>
 #include <botan/x509cert.h>
 #include <string>
@@ -20,6 +21,9 @@ namespace Botan {
 class X509_DN;
 class BigInt;
 
+/// @brief holds a PSK identity as used in TLS 1.3
+using PresharedKeyID = Strong<std::vector<uint8_t>, struct PresharedKeyID_>;
+
 /**
 * Interface for a credentials manager.
 *
diff --git a/src/lib/tls/tls13/info.txt b/src/lib/tls/tls13/info.txt
index efadcc179..0c3ca5f67 100644
--- a/src/lib/tls/tls13/info.txt
+++ b/src/lib/tls/tls13/info.txt
@@ -8,7 +8,7 @@ brief -> "TLS 1.3 protocol implementation"
 </module_info>
 
 <header:public>
-tls_ticket_13.h
+tls_psk_identity_13.h
 </header:public>
 
 <header:internal>
diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp
index 1ce7f9d57..3ccd0a7e6 100644
--- a/src/lib/tls/tls13/tls_cipher_state.cpp
+++ b/src/lib/tls/tls13/tls_cipher_state.cpp
@@ -129,8 +129,8 @@ std::unique_ptr<Cipher_State> Cipher_State::init_with_server_hello(const Connect
 std::unique_ptr<Cipher_State> Cipher_State::init_with_psk(const Connection_Side side,
                                                           const Cipher_State::PSK_Type type,
                                                           secure_vector<uint8_t>&& psk,
-                                                          const Ciphersuite& cipher) {
-   auto cs = std::unique_ptr<Cipher_State>(new Cipher_State(side, cipher.prf_algo()));
+                                                          std::string_view prf_algo) {
+   auto cs = std::unique_ptr<Cipher_State>(new Cipher_State(side, prf_algo));
    cs->advance_with_psk(type, std::move(psk));
    return cs;
 }
diff --git a/src/lib/tls/tls13/tls_cipher_state.h b/src/lib/tls/tls13/tls_cipher_state.h
index a91f9bff4..aed5f9a98 100644
--- a/src/lib/tls/tls13/tls_cipher_state.h
+++ b/src/lib/tls/tls13/tls_cipher_state.h
@@ -74,7 +74,7 @@ class BOTAN_TEST_API Cipher_State {
       static std::unique_ptr<Cipher_State> init_with_psk(const Connection_Side side,
                                                          const PSK_Type type,
                                                          secure_vector<uint8_t>&& psk,
-                                                         const Ciphersuite& cipher);
+                                                         std::string_view prf_algo);
 
       /**
        * Construct a Cipher_State after receiving a server hello message.
diff --git a/src/lib/tls/tls13/tls_extensions_psk.cpp b/src/lib/tls/tls13/tls_extensions_psk.cpp
index 54f11a2e8..90f954d6e 100644
--- a/src/lib/tls/tls13/tls_extensions_psk.cpp
+++ b/src/lib/tls/tls13/tls_extensions_psk.cpp
@@ -25,8 +25,49 @@ namespace Botan::TLS {
 
 namespace {
 
+decltype(auto) calculate_age(std::chrono::system_clock::time_point then, std::chrono::system_clock::time_point now) {
+   // TODO: Currently this does not provide actual millisecond resolution.
+   //       This might become a problem when "early data" is implemented and we
+   //       deal with servers that employ a strict "freshness" criteria on the
+   //       ticket's age.
+   return std::chrono::duration_cast<std::chrono::milliseconds>(now - then);
+}
+
 struct Client_PSK {
-      Ticket ticket;
+      Client_PSK(Session_with_Handle& session_to_resume, std::chrono::system_clock::time_point timestamp) :
+            Client_PSK(PskIdentity(session_to_resume.handle.opaque_handle(),
+                                   calculate_age(session_to_resume.session.start_time(), timestamp),
+                                   session_to_resume.session.session_age_add()),
+                       session_to_resume.session.ciphersuite().prf_algo(),
+                       session_to_resume.session.extract_master_secret(),
+                       Cipher_State::PSK_Type::Resumption) {}
+
+      Client_PSK(PskIdentity id, std::vector<uint8_t> bndr) : identity(std::move(id)), binder(std::move(bndr)) {}
+
+      Client_PSK(PskIdentity id,
+                 std::string_view prf_algo,
+                 secure_vector<uint8_t>&& master_secret,
+                 Cipher_State::PSK_Type psk_type) :
+            identity(std::move(id)),
+
+            // RFC 8446 4.2.11.2
+            //    Each entry in the binders list is computed as an HMAC over a transcript
+            //    hash (see Section 4.4.1) containing a partial ClientHello up to and
+            //    including the PreSharedKeyExtension.identities field.  That is, it
+            //    includes all of the ClientHello but not the binders list itself.  The
+            //    length fields for the message (including the overall length, the length
+            //    of the extensions block, and the length of the "pre_shared_key"
+            //    extension) are all set as if binders of the correct lengths were
+            //    present.
+            //
+            // Hence, we fill the binders with dummy values of the correct length and use
+            // `Client_Hello_13::truncate()` to split them off before calculating the
+            // transcript hash that underpins the PSK binders. S.a. `calculate_binders()`
+            binder(HashFunction::create_or_throw(prf_algo)->output_length()),
+            cipher_state(
+               Cipher_State::init_with_psk(Connection_Side::Client, psk_type, std::move(master_secret), prf_algo)) {}
+
+      PskIdentity identity;
       std::vector<uint8_t> binder;
 
       // Clients set up associated cipher states for PSKs
@@ -63,20 +104,17 @@ PSK::PSK(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type messag
       m_impl = std::make_unique<PSK_Internal>(
          Server_PSK{.selected_identity = reader.get_uint16_t(), .session_to_resume = std::nullopt});
    } else if(message_type == Handshake_Type::ClientHello) {
-      std::vector<Client_PSK> psks;
-
       const auto identities_length = reader.get_uint16_t();
       const auto identities_offset = reader.read_so_far();
 
+      std::vector<PskIdentity> psk_identities;
       while(reader.has_remaining() && (reader.read_so_far() - identities_offset) < identities_length) {
-         auto identity = Opaque_Session_Handle(reader.get_tls_length_value(2));
+         auto identity = reader.get_tls_length_value(2);
          const auto obfuscated_ticket_age = reader.get_uint32_t();
-
-         psks.emplace_back(Client_PSK{
-            .ticket = Ticket(std::move(identity), obfuscated_ticket_age), .binder = {}, .cipher_state = nullptr});
+         psk_identities.emplace_back(std::move(identity), obfuscated_ticket_age);
       }
 
-      if(psks.empty()) {
+      if(psk_identities.empty()) {
          throw TLS_Exception(Alert::DecodeError, "Empty PSK list");
       }
 
@@ -91,12 +129,13 @@ PSK::PSK(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type messag
          throw TLS_Exception(Alert::DecodeError, "Empty PSK binders list");
       }
 
-      for(auto& psk : psks) {
+      std::vector<Client_PSK> psks;
+      for(auto& psk_identity : psk_identities) {
          if(!reader.has_remaining() || reader.read_so_far() - binders_offset >= binders_length) {
             throw TLS_Exception(Alert::IllegalParameter, "Not enough PSK binders");
          }
 
-         psk.binder = reader.get_tls_length_value(1);
+         psks.emplace_back(std::move(psk_identity), reader.get_tls_length_value(1));
       }
 
       if(reader.read_so_far() - binders_offset != binders_length) {
@@ -110,38 +149,8 @@ PSK::PSK(TLS_Data_Reader& reader, uint16_t extension_size, Handshake_Type messag
 }
 
 PSK::PSK(Session_with_Handle& session_to_resume, Callbacks& callbacks) {
-   // RFC 8446 4.2.11.2
-   //    Each entry in the binders list is computed as an HMAC over a transcript
-   //    hash (see Section 4.4.1) containing a partial ClientHello up to and
-   //    including the PreSharedKeyExtension.identities field.  That is, it
-   //    includes all of the ClientHello but not the binders list itself.  The
-   //    length fields for the message (including the overall length, the length
-   //    of the extensions block, and the length of the "pre_shared_key"
-   //    extension) are all set as if binders of the correct lengths were
-   //    present.
-   //
-   // Hence, we fill the binders with dummy values of the correct length and use
-   // `Client_Hello_13::truncate()` to split them off before calculating the
-   // transcript hash that underpins the PSK binders. S.a. `calculate_binders()`
-   const auto cipher = session_to_resume.session.ciphersuite();
-   const auto binder_length = HashFunction::create_or_throw(cipher.prf_algo())->output_length();
-
-   // TODO: Currently this does not provide actual millisecond resolution.
-   //       This might become a problem when "early data" is implemented and we
-   //       deal with servers that employ a strict "freshness" criteria on the
-   //       ticket's age.
-   const auto age = std::chrono::duration_cast<std::chrono::milliseconds>(callbacks.tls_current_timestamp() -
-                                                                          session_to_resume.session.start_time());
-
    std::vector<Client_PSK> cpsk;
-   cpsk.emplace_back(Client_PSK{
-      .ticket = Ticket(session_to_resume.handle.opaque_handle(), age, session_to_resume.session.session_age_add()),
-      .binder = std::vector<uint8_t>(binder_length),
-      .cipher_state = Cipher_State::init_with_psk(Connection_Side::Client,
-                                                  Cipher_State::PSK_Type::Resumption,
-                                                  session_to_resume.session.extract_master_secret(),
-                                                  cipher)});
-
+   cpsk.emplace_back(session_to_resume, callbacks.tls_current_timestamp());
    m_impl = std::make_unique<PSK_Internal>(std::move(cpsk));
 }
 
@@ -201,10 +210,11 @@ std::unique_ptr<PSK> PSK::select_offered_psk(const Ciphersuite& cipher,
    BOTAN_STATE_CHECK(std::holds_alternative<std::vector<Client_PSK>>(m_impl->psk));
 
    auto& psks = std::get<std::vector<Client_PSK>>(m_impl->psk);
-   std::vector<Ticket> tickets;
-   std::transform(psks.begin(), psks.end(), std::back_inserter(tickets), [&](const auto& psk) { return psk.ticket; });
+   std::vector<PskIdentity> psk_identities;
+   std::transform(
+      psks.begin(), psks.end(), std::back_inserter(psk_identities), [&](const auto& psk) { return psk.identity; });
 
-   if(auto selection = session_mgr.choose_from_offered_tickets(tickets, cipher.prf_algo(), callbacks, policy)) {
+   if(auto selection = session_mgr.choose_from_offered_tickets(psk_identities, cipher.prf_algo(), callbacks, policy)) {
       auto& [session, psk_index] = selection.value();
 
       // RFC 8446 4.6.1
@@ -258,9 +268,9 @@ std::vector<uint8_t> PSK::serialize(Connection_Side side) const {
                     std::vector<uint8_t> identities;
                     std::vector<uint8_t> binders;
                     for(const auto& psk : psks) {
-                       append_tls_length_value(identities, psk.ticket.identity().get(), 2);
+                       append_tls_length_value(identities, psk.identity.identity(), 2);
 
-                       const auto obfuscated_ticket_age = psk.ticket.obfuscated_age();
+                       const auto obfuscated_ticket_age = psk.identity.obfuscated_age();
                        identities.push_back(get_byte<0>(obfuscated_ticket_age));
                        identities.push_back(get_byte<1>(obfuscated_ticket_age));
                        identities.push_back(get_byte<2>(obfuscated_ticket_age));
diff --git a/src/lib/tls/tls13/tls_ticket_13.cpp b/src/lib/tls/tls13/tls_psk_identity_13.cpp
similarity index 67%
rename from src/lib/tls/tls13/tls_ticket_13.cpp
rename to src/lib/tls/tls13/tls_psk_identity_13.cpp
index f8dc8a5e3..309a3bc31 100644
--- a/src/lib/tls/tls13/tls_ticket_13.cpp
+++ b/src/lib/tls/tls13/tls_psk_identity_13.cpp
@@ -6,7 +6,7 @@
  * Botan is released under the Simplified BSD License (see license.txt)
  */
 
-#include <botan/tls_ticket_13.h>
+#include <botan/tls_psk_identity_13.h>
 
 namespace Botan::TLS {
 
@@ -23,10 +23,12 @@ uint32_t obfuscate_ticket_age(const uint64_t in, const uint64_t ticket_age_add)
 
 }  // namespace
 
-Ticket::Ticket(Opaque_Session_Handle identity, const std::chrono::milliseconds age, const uint32_t ticket_age_add) :
-      Ticket(std::move(identity), obfuscate_ticket_age(age.count(), ticket_age_add)) {}
+PskIdentity::PskIdentity(Opaque_Session_Handle identity,
+                         const std::chrono::milliseconds age,
+                         const uint32_t ticket_age_add) :
+      PskIdentity(std::move(identity.get()), obfuscate_ticket_age(age.count(), ticket_age_add)) {}
 
-std::chrono::milliseconds Ticket::age(const uint32_t ticket_age_add) const {
+std::chrono::milliseconds PskIdentity::age(const uint32_t ticket_age_add) const {
    return std::chrono::milliseconds(obfuscate_ticket_age(m_obfuscated_age, ticket_age_add));
 }
 
diff --git a/src/lib/tls/tls13/tls_psk_identity_13.h b/src/lib/tls/tls13/tls_psk_identity_13.h
new file mode 100644
index 000000000..c6957d7bd
--- /dev/null
+++ b/src/lib/tls/tls13/tls_psk_identity_13.h
@@ -0,0 +1,78 @@
+/**
+ * Wrapper type for a TLS 1.3 session ticket
+ * (C) 2023 Jack Lloyd
+ *     2023 René Meusel - Rohde & Schwarz Cybersecurity
+ *
+ * Botan is released under the Simplified BSD License (see license.txt)
+ */
+
+#ifndef BOTAN_TLS_13_TICKET_H_
+#define BOTAN_TLS_13_TICKET_H_
+
+#include <botan/credentials_manager.h>
+#include <botan/tls_session.h>
+#include <botan/types.h>
+
+#include <chrono>
+#include <cstdint>
+#include <vector>
+
+namespace Botan::TLS {
+
+/**
+ * Represents a TLS 1.3 PSK identity as found in the Preshared Key extension
+ * with an opaque identity and an associated (obfuscated) ticket age. The latter
+ * is not applicable for externally provided PSKs.
+ */
+class BOTAN_PUBLIC_API(3, 1) PskIdentity {
+   public:
+      /**
+       * Construct from information provided in the peer's ClientHello
+       */
+      PskIdentity(std::vector<uint8_t> identity, const uint32_t obfuscated_age) :
+            m_identity(std::move(identity)), m_obfuscated_age(obfuscated_age) {}
+
+      /**
+       * Construct from a session stored by the client
+       */
+      PskIdentity(Opaque_Session_Handle identity, const std::chrono::milliseconds age, const uint32_t ticket_age_add);
+
+      /**
+       * Construct from an externally provided PSK in the client
+       */
+      PskIdentity(PresharedKeyID identity) :
+            m_identity(std::move(identity.get())),
+
+            // RFC 8446 4.2.11
+            //    For identities established externally, an obfuscated_ticket_age of
+            //    0 SHOULD be used, and servers MUST ignore the value.
+            m_obfuscated_age(0) {}
+
+      const std::vector<uint8_t>& identity() const { return m_identity; }
+
+      /**
+       * If this represents a PSK for session resumption, it returns the
+       * session's age given the de-obfuscation parameter @p ticket_age_add. For
+       * externally provided PSKs this method does not provide any meaningful
+       * information.
+       */
+      std::chrono::milliseconds age(const uint32_t ticket_age_add) const;
+
+      uint32_t obfuscated_age() const { return m_obfuscated_age; }
+
+   private:
+      std::vector<uint8_t> m_identity;
+      uint32_t m_obfuscated_age;
+};
+
+/**
+ * Botan 3.0.0 used the class name "Ticket". In Botan 3.1.0 we decided to
+ * re-name it to the more generic term "PskIdentity" to better reflect its dual
+ * use case for resumption and externally provided PSKs.
+ */
+BOTAN_DEPRECATED("Use PskIdentity")
+typedef PskIdentity Ticket;
+
+}  // namespace Botan::TLS
+
+#endif
diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp
index 362d1cdd9..059e33bd2 100644
--- a/src/lib/tls/tls13/tls_server_impl_13.cpp
+++ b/src/lib/tls/tls13/tls_server_impl_13.cpp
@@ -211,7 +211,7 @@ void Server_Impl_13::handle_reply_to_client_hello(Server_Hello_13 server_hello)
       psk_cipher_state = Cipher_State::init_with_psk(Connection_Side::Server,
                                                      Cipher_State::PSK_Type::Resumption,
                                                      m_resumed_session->extract_master_secret(),
-                                                     cipher);
+                                                     cipher.prf_algo());
 
       // RFC 8446 4.2.11
       //    Prior to accepting PSK key establishment, the server MUST validate
diff --git a/src/lib/tls/tls13/tls_ticket_13.h b/src/lib/tls/tls13/tls_ticket_13.h
deleted file mode 100644
index 83408aba2..000000000
--- a/src/lib/tls/tls13/tls_ticket_13.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * Wrapper type for a TLS 1.3 session ticket
- * (C) 2023 Jack Lloyd
- *     2023 René Meusel - Rohde & Schwarz Cybersecurity
- *
- * Botan is released under the Simplified BSD License (see license.txt)
- */
-
-#ifndef BOTAN_TLS_13_TICKET_H_
-#define BOTAN_TLS_13_TICKET_H_
-
-#include <botan/tls_session.h>
-#include <botan/types.h>
-
-#include <chrono>
-#include <cstdint>
-#include <vector>
-
-namespace Botan::TLS {
-
-/**
- * Represents a TLS 1.3 ticket with an opaque identity and an associated
- * (obfuscated) ticket age.
- *
- * RFC 8446 4.6.1
- *    The ticket itself is an opaque label. It MAY be either a database
- *    lookup key or a self-encrypted and self-authenticated value.
- */
-class BOTAN_PUBLIC_API(3, 0) Ticket {
-   public:
-      Ticket(Opaque_Session_Handle identity, const uint32_t obfuscated_age) :
-            m_identity(std::move(identity)), m_obfuscated_age(obfuscated_age) {}
-
-      Ticket(Opaque_Session_Handle identity, const std::chrono::milliseconds age, const uint32_t ticket_age_add);
-
-      const Opaque_Session_Handle& identity() const { return m_identity; }
-
-      std::chrono::milliseconds age(const uint32_t ticket_age_add) const;
-
-      uint32_t obfuscated_age() const { return m_obfuscated_age; }
-
-   private:
-      Opaque_Session_Handle m_identity;
-      uint32_t m_obfuscated_age;
-};
-
-}  // namespace Botan::TLS
-
-#endif
diff --git a/src/lib/tls/tls_session_manager.cpp b/src/lib/tls/tls_session_manager.cpp
index 25a4181e2..3b73a347a 100644
--- a/src/lib/tls/tls_session_manager.cpp
+++ b/src/lib/tls/tls_session_manager.cpp
@@ -204,12 +204,15 @@ std::vector<Session_with_Handle> Session_Manager::find(const Server_Information&
 #if defined(BOTAN_HAS_TLS_13)
 
 std::optional<std::pair<Session, uint16_t>> Session_Manager::choose_from_offered_tickets(
-   const std::vector<Ticket>& tickets, std::string_view hash_function, Callbacks& callbacks, const Policy& policy) {
+   const std::vector<PskIdentity>& tickets,
+   std::string_view hash_function,
+   Callbacks& callbacks,
+   const Policy& policy) {
    // Note that the TLS server currently does not ensure that tickets aren't
    // reused. As a result, no locking is required on this level.
 
    for(uint16_t i = 0; const auto& ticket : tickets) {
-      auto session = retrieve(ticket.identity(), callbacks, policy);
+      auto session = retrieve(Opaque_Session_Handle(ticket.identity()), callbacks, policy);
       if(session.has_value() && session->ciphersuite().prf_algo() == hash_function &&
          session->version().is_tls_13_or_later()) {
          return std::pair{std::move(session.value()), i};
diff --git a/src/lib/tls/tls_session_manager.h b/src/lib/tls/tls_session_manager.h
index ed66b2daa..a1e55eb4a 100644
--- a/src/lib/tls/tls_session_manager.h
+++ b/src/lib/tls/tls_session_manager.h
@@ -14,7 +14,7 @@
 #include <botan/types.h>
 
 #if defined(BOTAN_HAS_TLS_13)
-   #include <botan/tls_ticket_13.h>
+   #include <botan/tls_psk_identity_13.h>
 #endif
 
 #include <chrono>
@@ -116,7 +116,7 @@ class BOTAN_PUBLIC_API(3, 0) Session_Manager {
        * @note if no PSK is chosen, the server will attempt a regular handshake.
        */
       virtual std::optional<std::pair<Session, uint16_t>> choose_from_offered_tickets(
-         const std::vector<Ticket>& tickets,
+         const std::vector<PskIdentity>& tickets,
          std::string_view hash_function,
          Callbacks& callbacks,
          const Policy& policy);


Test patch:
diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp
index 3a9fee1b6..1875bb82b 100644
--- a/src/tests/test_tls_cipher_state.cpp
+++ b/src/tests/test_tls_cipher_state.cpp
@@ -546,11 +546,11 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt0() {
    auto cs_client = Cipher_State::init_with_psk(Connection_Side::Client,
                                                 Cipher_State::PSK_Type::Resumption,
                                                 secure_vector<uint8_t>(psk.begin(), psk.end()),
-                                                cipher);
+                                                cipher.prf_algo());
    auto cs_server = Cipher_State::init_with_psk(Connection_Side::Server,
                                                 Cipher_State::PSK_Type::Resumption,
                                                 secure_vector<uint8_t>(psk.begin(), psk.end()),
-                                                cipher);
+                                                cipher.prf_algo());
 
    auto CHECK_both = make_CHECK_both(cs_client.get(), cs_server.get());
 
diff --git a/src/tests/test_tls_session_manager.cpp b/src/tests/test_tls_session_manager.cpp
index a38547ad6..01a55a90c 100644
--- a/src/tests/test_tls_session_manager.cpp
+++ b/src/tests/test_tls_session_manager.cpp
@@ -418,7 +418,7 @@ std::vector<Test::Result> test_session_manager_choose_ticket() {
    };
 
    auto ticket = [&](std::span<const uint8_t> identity) {
-      return Botan::TLS::Ticket(Botan::TLS::Opaque_Session_Handle(identity), 0);
+      return Botan::TLS::PskIdentity(std::vector(identity.begin(), identity.end()), 0);
    };
 
    return {
@@ -1147,7 +1147,7 @@ std::vector<Test::Result> tls_session_manager_expiry() {
    #if defined(BOTAN_HAS_TLS_13)
          CHECK_all("expired tickets are not selected for PSK resumption", [&](auto, auto factory, auto& result) {
             auto ticket = [&](const Botan::TLS::Session_Handle& handle) {
-               return Botan::TLS::Ticket(handle.opaque_handle(), 0);
+               return Botan::TLS::PskIdentity(handle.opaque_handle().get(), 0);
             };
 
             auto mgr = factory();