Repository: randombit/botan
Pull request #3316 (2023-02-22 14:27:17)

[TLS 1.3] Cleanup Session Object
### Pull Request Dependencies

* ~~#3150~~ 
* ~~#3140~~

### Description

This splits the `Session` object into `Session_Summary` and `Session`. The latter is a serializable and resumable TLS session. The former will be used in a new callback replacing `Callbacks::tls_session_established()` in a protocol version agnostic way. See here: https://github.com/randombit/botan/pull/2988#issuecomment-1351250465 for an idea.

Also, it removes the default constructor in `Session`: Removing the possibility of partially filled or ill-formed session objects.

Finally, the `Session` encoding is slimmed down by eliminating a few legacy fields that were added for backward-compatibility only. With TLS 1.3 session backward compatibility is broken anyway; time for spring cleaning.

codecov-commenter: # [Codecov](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) Report
Patch coverage: **`90.27`**% and project coverage change: **`+0.10`** :tada:
> Comparison is base [(`67b8651`)](https://codecov.io/gh/randombit/botan/commit/67b865167e540205af6797964d397d2006126420?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) 88.04% compared to head [(`64ae735`)](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) 88.15%.

:mega: This organization is not using Codecov’s [GitHub App Integration](https://github.com/apps/codecov). We recommend you install it so Codecov can continue to function properly for your repositories. [Learn more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd)

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #3316      +/-   ##
==========================================
+ Coverage   88.04%   88.15%   +0.10%     
==========================================
  Files         615      615              
  Lines       69740    69942     +202     
  Branches     6913     6939      +26     
==========================================
+ Hits        61405    61658     +253     
+ Misses       5443     5387      -56     
- Partials     2892     2897       +5     
```


| [Impacted Files](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | Coverage Δ | |
|---|---|---|
| [...c/lib/tls/sessions\_sql/tls\_session\_manager\_sql.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvc2Vzc2lvbnNfc3FsL3Rsc19zZXNzaW9uX21hbmFnZXJfc3FsLmNwcA==) | `75.18% <0.00%> (ø)` | |
| [src/lib/tls/tls13/tls\_transcript\_hash\_13.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTMvdGxzX3RyYW5zY3JpcHRfaGFzaF8xMy5jcHA=) | `94.25% <0.00%> (ø)` | |
| [src/tests/test\_tls.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL3Rlc3RzL3Rlc3RfdGxzLmNwcA==) | `88.35% <ø> (-0.25%)` | :arrow_down: |
| [src/lib/tls/msg\_client\_hello.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvbXNnX2NsaWVudF9oZWxsby5jcHA=) | `78.67% <50.00%> (+0.86%)` | :arrow_up: |
| [src/lib/tls/tls\_server.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX3NlcnZlci5jcHA=) | `75.92% <50.00%> (-2.08%)` | :arrow_down: |
| [src/lib/tls/tls12/tls\_server\_impl\_12.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTIvdGxzX3NlcnZlcl9pbXBsXzEyLmNwcA==) | `79.23% <68.42%> (-0.62%)` | :arrow_down: |
| [src/lib/tls/msg\_server\_hello.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvbXNnX3NlcnZlcl9oZWxsby5jcHA=) | `82.98% <75.00%> (+1.13%)` | :arrow_up: |
| [src/lib/tls/tls\_session.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX3Nlc3Npb24uY3Bw) | `96.29% <75.00%> (+8.06%)` | :arrow_up: |
| [src/tests/test\_tls\_cipher\_state.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL3Rlc3RzL3Rlc3RfdGxzX2NpcGhlcl9zdGF0ZS5jcHA=) | `99.29% <85.71%> (-0.71%)` | :arrow_down: |
| [src/lib/tls/msg\_session\_ticket.cpp](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvbXNnX3Nlc3Npb25fdGlja2V0LmNwcA==) | `85.00% <87.50%> (+5.51%)` | :arrow_up: |
| ... and [36 more](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/randombit/botan/pull/3316?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).


Patch:
diff --git a/src/lib/tls/tls12/tls_server_impl_12.cpp b/src/lib/tls/tls12/tls_server_impl_12.cpp
index b8a1a19a0..d3a915d6b 100644
--- a/src/lib/tls/tls12/tls_server_impl_12.cpp
+++ b/src/lib/tls/tls12/tls_server_impl_12.cpp
@@ -60,39 +60,36 @@ class Server_Handshake_State final : public Handshake_State
 
 namespace {
 
-bool check_for_resume(const Session_Handle& handle_to_resume,
-                      Session& session_info,
-                      Session_Manager& session_manager,
-                      Callbacks& cb,
-                      const Policy& policy,
-                      const Client_Hello_12* client_hello)
+std::optional<Session> check_for_resume(const Session_Handle& handle_to_resume,
+                                        Session_Manager& session_manager,
+                                        Callbacks& cb,
+                                        const Policy& policy,
+                                        const Client_Hello_12* client_hello)
    {
    auto session = session_manager.retrieve(handle_to_resume, cb, policy);
    if(!session.has_value())
-      return false;
-
-   session_info = std::move(session.value());
+      return std::nullopt;
 
    // wrong version
-   if(client_hello->legacy_version() != session_info.version())
-      return false;
+   if(client_hello->legacy_version() != session->version())
+      return std::nullopt;
 
    // client didn't send original ciphersuite
    if(!value_exists(client_hello->ciphersuites(),
-                    session_info.ciphersuite_code()))
-      return false;
+                    session->ciphersuite_code()))
+      return std::nullopt;
 
    // client sent a different SNI hostname
    if(!client_hello->sni_hostname().empty() &&
-       client_hello->sni_hostname() != session_info.server_info().hostname())
-      return false;
+       client_hello->sni_hostname() != session->server_info().hostname())
+      return std::nullopt;
 
    // Checking extended_master_secret on resume (RFC 7627 section 5.3)
-   if(client_hello->supports_extended_master_secret() != session_info.supports_extended_master_secret())
+   if(client_hello->supports_extended_master_secret() != session->supports_extended_master_secret())
       {
-      if(!session_info.supports_extended_master_secret())
+      if(!session->supports_extended_master_secret())
          {
-         return false; // force new handshake with extended master secret
+         return std::nullopt; // force new handshake with extended master secret
          }
       else
          {
@@ -106,7 +103,7 @@ bool check_for_resume(const Session_Handle& handle_to_resume,
       }
 
    // Checking encrypt_then_mac on resume (RFC 7366 section 3.1)
-   if(!client_hello->supports_encrypt_then_mac() && session_info.supports_encrypt_then_mac())
+   if(!client_hello->supports_encrypt_then_mac() && session->supports_encrypt_then_mac())
       {
       /*
       Client previously negotiated session with Encrypt-then-MAC,
@@ -116,7 +113,7 @@ bool check_for_resume(const Session_Handle& handle_to_resume,
                              "Client resumed Encrypt-then-MAC session without sending extension");
       }
 
-   return true;
+   return session;
    }
 
 /*
@@ -474,16 +471,15 @@ void Server_Impl_12::process_client_hello_msg(const Handshake_State* active_stat
 
    const auto session_handle = pending_state.client_hello()->session_handle();
 
-   Session session_info;
-   const bool resuming =
-      pending_state.allow_session_resumption() &&
-      session_handle.has_value() &&
-      check_for_resume(session_handle.value(),
-                       session_info,
-                       session_manager(),
-                       callbacks(),
-                       policy(),
-                       pending_state.client_hello());
+   std::optional<Session> session_info;
+   if(pending_state.allow_session_resumption() && session_handle.has_value())
+      {
+      session_info = check_for_resume(session_handle.value(),
+                                      session_manager(),
+                                      callbacks(),
+                                      policy(),
+                                      pending_state.client_hello());
+      }
 
    m_next_protocol = "";
    if(pending_state.client_hello()->supports_alpn())
@@ -491,9 +487,9 @@ void Server_Impl_12::process_client_hello_msg(const Handshake_State* active_stat
       m_next_protocol = callbacks().tls_server_choose_app_protocol(pending_state.client_hello()->next_protocols());
       }
 
-   if(resuming)
+   if(session_info.has_value())
       {
-      this->session_resume(pending_state, {session_info, session_handle.value()});
+      this->session_resume(pending_state, {session_info.value(), session_handle.value()});
       }
    else // new session
       {
diff --git a/src/lib/tls/tls_session.cpp b/src/lib/tls/tls_session.cpp
index 9e12ffe67..07ffb7ee0 100644
--- a/src/lib/tls/tls_session.cpp
+++ b/src/lib/tls/tls_session.cpp
@@ -88,19 +88,17 @@ std::optional<Session_Ticket> Session_Handle::ticket() const
    }
 
 
-Session::Session() :
-   m_start_time(std::chrono::system_clock::time_point::min()),
-   m_version(),
-   m_ciphersuite(0),
-   m_connection_side(static_cast<Connection_Side>(0)),
-   m_srtp_profile(0),
-   m_extended_master_secret(false),
-   m_encrypt_then_mac(false),
-   m_early_data_allowed(false),
-   m_max_early_data_bytes(0),
-   m_ticket_age_add(0),
-   m_lifetime_hint(0)
-   {}
+Ciphersuite Session_Summary::ciphersuite() const
+   {
+   auto suite = Ciphersuite::by_id(m_ciphersuite);
+   if (!suite.has_value())
+      {
+      throw Decoding_Error("Failed to find cipher suite for ID " +
+                           std::to_string(m_ciphersuite));
+      }
+   return suite.value();
+   }
+
 
 Session::Session(const secure_vector<uint8_t>& master_secret,
                  Protocol_Version version,
@@ -113,16 +111,17 @@ Session::Session(const secure_vector<uint8_t>& master_secret,
                  uint16_t srtp_profile,
                  std::chrono::system_clock::time_point current_timestamp,
                  std::chrono::seconds lifetime_hint) :
-   m_start_time(current_timestamp),
+   Session_Summary(
+      current_timestamp,
+      version,
+      ciphersuite,
+      side,
+      srtp_profile,
+      extended_master_secret,
+      encrypt_then_mac,
+      certs,
+      server_info),
    m_master_secret(master_secret),
-   m_version(version),
-   m_ciphersuite(ciphersuite),
-   m_connection_side(side),
-   m_srtp_profile(srtp_profile),
-   m_extended_master_secret(extended_master_secret),
-   m_encrypt_then_mac(encrypt_then_mac),
-   m_peer_certs(certs),
-   m_server_info(server_info),
    m_early_data_allowed(false),
    m_max_early_data_bytes(0),
    m_ticket_age_add(0),
@@ -144,26 +143,27 @@ Session::Session(const secure_vector<uint8_t>& session_psk,
                  const std::vector<X509_Certificate>& peer_certs,
                  const Server_Information& server_info,
                  std::chrono::system_clock::time_point current_timestamp) :
-   m_start_time(current_timestamp),
+   Session_Summary(
+      current_timestamp,
+      version,
+      ciphersuite,
+      side,
+
+      // TODO: SRTP might become necessary when DTLS 1.3 is being implemented
+      0,
+
+      // RFC 8446 Appendix D
+      //    Because TLS 1.3 always hashes in the transcript up to the server
+      //    Finished, implementations which support both TLS 1.3 and earlier
+      //    versions SHOULD indicate the use of the Extended Master Secret
+      //    extension in their APIs whenever TLS 1.3 is used.
+      true,
+
+      // TLS 1.3 uses AEADs, so technically encrypt-then-MAC is not applicable.
+      false,
+      peer_certs,
+      server_info),
    m_master_secret(session_psk),
-   m_version(version),
-   m_ciphersuite(ciphersuite),
-   m_connection_side(side),
-
-   // TODO: Might become necessary when DTLS 1.3 is being implemented.
-   m_srtp_profile(0),
-
-   // RFC 8446 Appendix D
-   //    Because TLS 1.3 always hashes in the transcript up to the server
-   //    Finished, implementations which support both TLS 1.3 and earlier
-   //    versions SHOULD indicate the use of the Extended Master Secret
-   //    extension in their APIs whenever TLS 1.3 is used.
-   m_extended_master_secret(true),
-
-   // TLS 1.3 uses AEADs, so technically encrypt-then-MAC is not applicable.
-   m_encrypt_then_mac(false),
-   m_peer_certs(peer_certs),
-   m_server_info(server_info),
    m_early_data_allowed(max_early_data_bytes.has_value()),
    m_max_early_data_bytes(max_early_data_bytes.value_or(0)),
    m_ticket_age_add(ticket_age_add),
@@ -181,15 +181,15 @@ Session::Session(secure_vector<uint8_t>&& session_psk,
                  const Server_Hello_13& server_hello,
                  Callbacks& callbacks,
                  RandomNumberGenerator& rng) :
-   m_start_time(callbacks.tls_current_timestamp()),
+   Session_Summary(
+      callbacks.tls_current_timestamp(),
+      server_hello.selected_version(),
+      server_hello.ciphersuite(),
+      Connection_Side::Server,
+      0, true, false,  // see constructor above for rationales
+      peer_certs,
+      client_hello.sni_hostname()),
    m_master_secret(std::move(session_psk)),
-   m_version(server_hello.selected_version()),
-   m_ciphersuite(server_hello.ciphersuite()),
-   m_connection_side(Connection_Side::Server),
-   m_extended_master_secret(true),
-   m_encrypt_then_mac(true),
-   m_peer_certs(peer_certs),
-   m_server_info(client_hello.sni_hostname()),
    m_early_data_allowed(max_early_data_bytes.has_value()),
    m_max_early_data_bytes(max_early_data_bytes.value_or(0)),
    m_ticket_age_add(load_be<uint32_t>(rng.random_vec(4).data(), 0)),
@@ -212,15 +212,10 @@ Session::Session(std::span<const uint8_t> ber_data)
    ASN1_String server_service;
    size_t server_port;
 
-   ASN1_String srp_identifier_str;
-
    uint8_t major_version = 0, minor_version = 0;
-   std::vector<uint8_t> peer_cert_bits;
 
    size_t start_time = 0;
    size_t srtp_profile = 0;
-   size_t fragment_size = 0;
-   size_t compression_method = 0;
    uint16_t ciphersuite_code = 0;
    uint64_t lifetime_hint = 0;
 
@@ -232,9 +227,7 @@ Session::Session(std::span<const uint8_t> ber_data)
         .decode_integer_type(major_version)
         .decode_integer_type(minor_version)
         .decode_integer_type(ciphersuite_code)
-        .decode_integer_type(compression_method)
         .decode_integer_type(side_code)
-        .decode_integer_type(fragment_size)
         .decode(m_extended_master_secret)
         .decode(m_encrypt_then_mac)
         .decode(m_master_secret, ASN1_Type::OctetString)
@@ -242,7 +235,6 @@ Session::Session(std::span<const uint8_t> ber_data)
         .decode(server_hostname)
         .decode(server_service)
         .decode(server_port)
-        .decode(srp_identifier_str)
         .decode(srtp_profile)
         .decode(m_early_data_allowed)
         .decode_integer_type(m_max_early_data_bytes)
@@ -251,24 +243,6 @@ Session::Session(std::span<const uint8_t> ber_data)
       .end_cons()
       .verify_end();
 
-   /*
-   * Compression is not supported and must be zero
-   */
-   if(compression_method != 0)
-      {
-      throw Decoding_Error("Serialized TLS session contains non-null compression method");
-      }
-
-   /*
-   Fragment size is not supported anymore, but the field is still
-   set in the session object.
-   */
-   if(fragment_size != 0)
-      {
-      throw Decoding_Error("Serialized TLS session used maximum fragment length which is "
-                           " no longer supported");
-      }
-
    if(!Ciphersuite::by_id(ciphersuite_code))
       {
       throw Decoding_Error("Serialized TLS session contains unknown cipher suite "
@@ -297,9 +271,7 @@ secure_vector<uint8_t> Session::DER_encode() const
          .encode(static_cast<size_t>(m_version.major_version()))
          .encode(static_cast<size_t>(m_version.minor_version()))
          .encode(static_cast<size_t>(m_ciphersuite))
-         .encode(static_cast<size_t>(/*old compression method*/0))
          .encode(static_cast<size_t>(m_connection_side))
-         .encode(static_cast<size_t>(/*old fragment size*/0))
          .encode(m_extended_master_secret)
          .encode(m_encrypt_then_mac)
          .encode(m_master_secret, ASN1_Type::OctetString)
@@ -309,7 +281,6 @@ secure_vector<uint8_t> Session::DER_encode() const
          .encode(ASN1_String(m_server_info.hostname(), ASN1_Type::Utf8String))
          .encode(ASN1_String(m_server_info.service(), ASN1_Type::Utf8String))
          .encode(static_cast<size_t>(m_server_info.port()))
-         .encode(ASN1_String("", ASN1_Type::Utf8String)) // old srp identifier
          .encode(static_cast<size_t>(m_srtp_profile))
 
          // the fields below were introduced for TLS 1.3 session tickets
@@ -326,17 +297,6 @@ std::string Session::PEM_encode() const
    return PEM_Code::encode(this->DER_encode(), "TLS SESSION");
    }
 
-Ciphersuite Session::ciphersuite() const
-   {
-   auto suite = Ciphersuite::by_id(m_ciphersuite);
-   if (!suite.has_value())
-      {
-      throw Decoding_Error("Failed to find cipher suite for ID " +
-                           std::to_string(m_ciphersuite));
-      }
-   return suite.value();
-   }
-
 secure_vector<uint8_t> Session::extract_master_secret()
    {
    BOTAN_STATE_CHECK(!m_master_secret.empty());
@@ -413,20 +373,21 @@ Session::encrypt(const SymmetricKey& key, RandomNumberGenerator& rng) const
    return buf;
    }
 
-Session Session::decrypt(const uint8_t in[], size_t in_len, const SymmetricKey& key)
+Session Session::decrypt(std::span<const uint8_t> in, const SymmetricKey& key)
    {
    try
       {
       const size_t min_session_size = 48 + 4; // serious under-estimate
-      if(in_len < TLS_SESSION_CRYPT_OVERHEAD + min_session_size)
+      if(in.size() < TLS_SESSION_CRYPT_OVERHEAD + min_session_size)
          throw Decoding_Error("Encrypted session too short to be valid");
 
-      const uint8_t* magic = &in[0];
+      // TODO: replace those raw pointers with sub-spans into `in`
+      const uint8_t* magic = in.data();
       const uint8_t* key_name = magic + TLS_SESSION_CRYPT_MAGIC_LEN;
       const uint8_t* key_seed = key_name + TLS_SESSION_CRYPT_KEY_NAME_LEN;
       const uint8_t* aead_nonce = key_seed + TLS_SESSION_CRYPT_AEAD_KEY_SEED_LEN;
       const uint8_t* ctext = aead_nonce + TLS_SESSION_CRYPT_AEAD_NONCE_LEN;
-      const size_t ctext_len = in_len - TLS_SESSION_CRYPT_HDR_LEN; // includes the tag
+      const size_t ctext_len = in.size() - TLS_SESSION_CRYPT_HDR_LEN; // includes the tag
 
       if(load_be<uint64_t>(magic, 0) != TLS_SESSION_CRYPT_MAGIC)
          throw Decoding_Error("Missing expected magic numbers");
@@ -447,7 +408,7 @@ Session Session::decrypt(const uint8_t in[], size_t in_len, const SymmetricKey&
 
       auto aead = AEAD_Mode::create_or_throw(TLS_SESSION_CRYPT_AEAD, Cipher_Dir::Decryption);
       aead->set_key(aead_key);
-      aead->set_associated_data(in, TLS_SESSION_CRYPT_HDR_LEN);
+      aead->set_associated_data(in.data(), TLS_SESSION_CRYPT_HDR_LEN);
       aead->start(aead_nonce, TLS_SESSION_CRYPT_AEAD_NONCE_LEN);
       secure_vector<uint8_t> buf(ctext, ctext + ctext_len);
       aead->finish(buf, 0);
diff --git a/src/lib/tls/tls_session.h b/src/lib/tls/tls_session.h
index 06fcecc9b..04bcde8de 100644
--- a/src/lib/tls/tls_session.h
+++ b/src/lib/tls/tls_session.h
@@ -136,17 +136,109 @@ class Server_Hello_13;
 class Callbacks;
 
 /**
-* Class representing a TLS session state
-*/
-class BOTAN_PUBLIC_API(2,0) Session final
+ * Represents basic information about a session that can be both
+ * persisted for resumption and presented to the application as
+ * a summary of a specific just-established TLS session.
+ */
+class BOTAN_PUBLIC_API(3,0) Session_Summary
    {
    public:
+      Session_Summary(std::chrono::system_clock::time_point start_time,
+                      Protocol_Version version,
+                      uint16_t ciphersuite,
+                      Connection_Side connection_side,
+                      uint16_t srtp_profile,
+                      bool extended_master_secret,
+                      bool encrypt_then_mac,
+                      std::vector<X509_Certificate> peer_certs,
+                      Server_Information server_info)
+         : m_start_time(start_time)
+         , m_version(version)
+         , m_ciphersuite(ciphersuite)
+         , m_connection_side(connection_side)
+         , m_srtp_profile(srtp_profile)
+         , m_extended_master_secret(extended_master_secret)
+         , m_encrypt_then_mac(encrypt_then_mac)
+         , m_peer_certs(std::move(peer_certs))
+         , m_server_info(std::move(server_info)) {}
+
+   protected:
+      Session_Summary() {}
 
+   public:
       /**
-      * Uninitialized session
-      */
-      Session();
+       * Get the wall clock time this session began
+       */
+      std::chrono::system_clock::time_point start_time() const { return m_start_time; }
+
+      /**
+       * Get the negotiated protocol version of the TLS session
+       */
+      Protocol_Version version() const { return m_version; }
+
+      /**
+       * Get the ciphersuite code of the negotiated TLS session
+       */
+      uint16_t ciphersuite_code() const { return m_ciphersuite; }
+
+      /**
+       * Get the ciphersuite info of the negotiated TLS session
+       */
+      Ciphersuite ciphersuite() const;
+
+      /**
+       * Get which side of the connection we are/were acting as.
+       */
+      Connection_Side side() const { return m_connection_side; }
+
+      /**
+       * Get the negotiated DTLS-SRTP algorithm (RFC 5764)
+       */
+      uint16_t dtls_srtp_profile() const { return m_srtp_profile; }
+
+      /**
+       * Returns true if a TLS 1.2 session negotiated "encrypt then MAC";
+       * TLS 1.3 sessions will always return false as they always use an AEAD.
+       */
+      bool supports_encrypt_then_mac() const { return m_encrypt_then_mac; }
+
+      /**
+       * Returns true if a TLS 1.2 session negotiated "extended master secret";
+       * TLS 1.3 sessions will always return true (see RFC 8446 Appendix D).
+       */
+      bool supports_extended_master_secret() const { return m_extended_master_secret; }
+
+      /**
+       * Return the certificate chain of the peer (possibly empty)
+       */
+      const std::vector<X509_Certificate>& peer_certs() const { return m_peer_certs; }
+
+      /**
+       * Get information about the TLS server
+       */
+      const Server_Information& server_info() const { return m_server_info; }
+
+   protected:
+      std::chrono::system_clock::time_point m_start_time;
+
+      Protocol_Version m_version;
+      uint16_t m_ciphersuite;
+      Connection_Side m_connection_side;
+      uint16_t m_srtp_profile;
+
+      bool m_extended_master_secret;
+      bool m_encrypt_then_mac;
+
+      std::vector<X509_Certificate> m_peer_certs;
+      Server_Information m_server_info;
+   };
 
+/**
+* Class representing a TLS session state
+*/
+class BOTAN_PUBLIC_API(2,0) Session final : public Session_Summary
+   {
+   public:
       /**
       * New TLS 1.2 session (sets session start time)
       */
@@ -218,27 +310,23 @@ class BOTAN_PUBLIC_API(2,0) Session final
       std::vector<uint8_t> encrypt(const SymmetricKey& key,
                                 RandomNumberGenerator& rng) const;
 
-
       /**
       * Decrypt a session created by encrypt
       * @param ctext the ciphertext returned by encrypt
       * @param ctext_size the size of ctext in bytes
       * @param key the same key used by the encrypting side
       */
-      static Session decrypt(const uint8_t ctext[],
-                             size_t ctext_size,
-                             const SymmetricKey& key);
+      static inline Session decrypt(const uint8_t ctext[],
+                                    size_t ctext_size,
+                                    const SymmetricKey& key)
+         { return Session::decrypt(std::span(ctext, ctext_size), key); }
 
       /**
       * Decrypt a session created by encrypt
       * @param ctext the ciphertext returned by encrypt
       * @param key the same key used by the encrypting side
       */
-      static inline Session decrypt(std::span<const uint8_t> ctext,
-                                    const SymmetricKey& key)
-         {
-         return Session::decrypt(ctext.data(), ctext.size(), key);
-         }
+      static Session decrypt(std::span<const uint8_t> ctext, const SymmetricKey& key);
 
       /**
       * Encode this session data for storage
@@ -247,27 +335,6 @@ class BOTAN_PUBLIC_API(2,0) Session final
       */
       std::string PEM_encode() const;
 
-      /**
-      * Get the version of the saved session
-      */
-      Protocol_Version version() const { return m_version; }
-
-      /**
-      * Get the ciphersuite code of the saved session
-      */
-      uint16_t ciphersuite_code() const { return m_ciphersuite; }
-
-      /**
-      * Get the ciphersuite info of the saved session
-      */
-      Ciphersuite ciphersuite() const;
-
-      /**
-      * Get which side of the connection the resumed session we are/were
-      * acting as.
-      */
-      Connection_Side side() const { return m_connection_side; }
-
       /**
       * Get a reference to the contained master secret
       */
@@ -279,26 +346,10 @@ class BOTAN_PUBLIC_API(2,0) Session final
       secure_vector<uint8_t> extract_master_secret();
 
       /**
-      * Get the negotiated DTLS-SRTP algorithm (RFC 5764)
-      */
-      uint16_t dtls_srtp_profile() const { return m_srtp_profile; }
-
-      bool supports_extended_master_secret() const { return m_extended_master_secret; }
-
-      bool supports_encrypt_then_mac() const { return m_encrypt_then_mac; }
-
+       * Get whether the saved session supports sending/receiving of early data
+       */
       bool supports_early_data() const { return m_early_data_allowed; }
 
-      /**
-      * Return the certificate chain of the peer (possibly empty)
-      */
-      const std::vector<X509_Certificate>& peer_certs() const { return m_peer_certs; }
-
-      /**
-      * Get the wall clock time this session began
-      */
-      std::chrono::system_clock::time_point start_time() const { return m_start_time; }
-
       /**
       * Return the ticket obfuscation adder
       */
@@ -309,11 +360,6 @@ class BOTAN_PUBLIC_API(2,0) Session final
       */
       uint32_t max_early_data_bytes() const { return m_max_early_data_bytes; }
 
-      /**
-      * @return information about the TLS server
-      */
-      const Server_Information& server_info() const { return m_server_info; }
-
       /**
       * @return the lifetime of the ticket as defined by the TLS server
       */
@@ -332,25 +378,17 @@ class BOTAN_PUBLIC_API(2,0) Session final
       // 20230112 - Remove Session_ID and Session_Ticket from this object
       //            (association is now in the hands of the Session_Manager)
       //          - Peer certificates are now stored as a SEQUENCE
+      // 20230222 - Remove deprecated and unused fields
+      //            - compression method (always 0)
+      //            - fragment size (always 0)
+      //            - SRP identifier (always "")
       enum
          {
-         TLS_SESSION_PARAM_STRUCT_VERSION = 20230112
+         TLS_SESSION_PARAM_STRUCT_VERSION = 20230222
          };
 
-      std::chrono::system_clock::time_point m_start_time;
-
       secure_vector<uint8_t> m_master_secret;
 
-      Protocol_Version m_version;
-      uint16_t m_ciphersuite;
-      Connection_Side m_connection_side;
-      uint16_t m_srtp_profile;
-      bool m_extended_master_secret;
-      bool m_encrypt_then_mac;
-
-      std::vector<X509_Certificate> m_peer_certs;
-      Server_Information m_server_info; // optional
-
       bool m_early_data_allowed;
       uint32_t m_max_early_data_bytes;
       uint32_t m_ticket_age_add;


Test patch:
diff --git a/src/tests/data/tls_13_rfc8448/transcripts.vec b/src/tests/data/tls_13_rfc8448/transcripts.vec
index 99f443f96..33785ce36 100644
--- a/src/tests/data/tls_13_rfc8448/transcripts.vec
+++ b/src/tests/data/tls_13_rfc8448/transcripts.vec
@@ -15,7 +15,7 @@ Message_Server_Finished = 140000209b9b141d906337fbd2cbdce71df4deda4ab42c309572cb
 Record_ClientFinished = 170303003575ec4dc238cce60b298044a71e219c56cc77b0517fe9b93c7a4bfc44d87f38f80338ac98fc46deb384bd1caeacab6867d726c40546
 Record_NewSessionTicket = 17030300de3a6b8f90414a97d6959c3487680de5134a2b240e6cffac116e95d41d6af8f6b580dcf3d11d63c758db289a015940252f55713e061dc13e078891a38efbcf5753ad8ef170ad3c7353d16d9da773b9ca7f2b9fa1b6c0d4a3d03f75e09c30ba1e62972ac46f75f7b981be63439b2999ce13064615139891d5e4c5b406f16e3fc181a77ca475840025db2f0a77f81b5ab05b94c01346755f69232c86519d86cbeeac87aac347d143f9605d64f650db4d023e70e952ca49fe5137121c74bc2697687e248746d6df353005f3bce18696129c8153556b3b6c6779b37bf15985684f
 SessionTicket = 2c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357
-Client_SessionData = 3082021E02040134AFE002046274DF72020103020104020213010201000201010201000101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE10C067365727665720C000201000C000201000101FF02020400020500FAD6AAC502011E
+Client_SessionData = 3082021602040134B04E02046274DF72020103020104020213010201010101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE10C067365727665720C000201000201000101FF02020400020500FAD6AAC502011E
 Client_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031
 Record_Client_AppData = 1703030043a23f7054b62c94d0affafe8228ba55cbefacea42f914aa66bcab3f2b9819a8a5b46b395bd54a9a20441e2b62974e1f5a6292a2977014bd1e3deae63aeebb21694915e4
 Server_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031
@@ -28,7 +28,7 @@ Client_RNG_Pool = 1bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d
 Server_RNG_Pool = 3ccfd2dec890222763472ae8136777c9d7358777bb66e91ea5122495f559ea2dde5b4476e7b490b2652d338acbf2948066f255f9440e23b98fc69835298dc107
 CurrentTimestamp = 1651826546006
 SessionTicket = 2c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357
-Client_SessionData = 3082021E02040134AFE002046274DF72020103020104020213010201000201010201000101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE10C067365727665720C000201000C000201000101FF02020400020500FAD6AAC502011E
+Client_SessionData = 3082021602040134B04E02046274DF72020103020104020213010201010101FF01010004204ECD0EB6EC3B4D87F5D6028F922CA4C5851A277FD41311C9E62D2C9492E1C4F3308201B0308201AC30820115A003020102020102300D06092A864886F70D01010B0500300E310C300A06035504031303727361301E170D3136303733303031323335395A170D3236303733303031323335395A300E310C300A0603550403130372736130819F300D06092A864886F70D010101050003818D0030818902818100B4BB498F8279303D980836399B36C6988C0C68DE55E1BDB826D3901A2461EAFD2DE49A91D015ABBC9A95137ACE6C1AF19EAA6AF98C7CED43120998E187A80EE0CCB0524B1B018C3E0B63264D449A6D38E22A5FDA430846748030530EF0461C8CA9D9EFBFAE8EA6D1D03E2BD193EFF0AB9A8002C47428A6D35A8D88D79F7F1E3F0203010001A31A301830090603551D1304023000300B0603551D0F0404030205A0300D06092A864886F70D01010B05000381810085AAD2A0E5B9276B908C65F73A7267170618A54C5F8A7B337D2DF7A594365417F2EAE8F8A58C8F8172F9319CF36B7FD6C55B80F21A03015156726096FD335E5E67F2DBF102702E608CCAE6BEC1FC63A42A99BE5C3EB7107C3C54E9B9EB2BD5203B1C3B84E0A8B2F759409BA3EAC9D91D402DCC0CC8F8961229AC9187B42B4DE10C067365727665720C000201000201000101FF02020400020500FAD6AAC502011E
 Record_ClientHello_1 = 1603010200010001fc03031bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d95000006130113031302010001cd0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d0020e4ffb68ac05f8d96c99da26698346c6be16482badddafe051a66b4f18d668f0b002a0000002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c0002400100150057000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002900dd00b800b22c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357fad6aacb0021203add4fb2d8fdf822a0ca3cf7678ef5e88dae990141c5924d57bb6fa31b9e5f9d
 Client_EarlyAppData = 414243444546
 Record_Client_EarlyAppData = 1703030017ab1df420e75c457a7cc5d2844f76d5aee4b4edbf049be0
diff --git a/src/tests/test_tls.cpp b/src/tests/test_tls.cpp
index 82795485c..f748cea30 100644
--- a/src/tests/test_tls.cpp
+++ b/src/tests/test_tls.cpp
@@ -34,14 +34,6 @@ class TLS_Session_Tests final : public Test
          {
          Test::Result result("TLS::Session");
 
-         Botan::TLS::Session default_session;
-
-         Botan::secure_vector<uint8_t> default_der = default_session.DER_encode();
-
-         result.test_gte("Encoded default session has size", default_der.size(), 0);
-         result.test_throws("Encoded default session cannot be read",
-                            [&] { Botan::TLS::Session{default_der}; });
-
          Botan::TLS::Session session(Botan::secure_vector<uint8_t>{0xCC, 0xDD},
                                      Botan::TLS::Protocol_Version::TLS_V12,
                                      0xC02F,
diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp
index 59caab9f4..bf421300d 100644
--- a/src/tests/test_tls_rfc8448.cpp
+++ b/src/tests/test_tls_rfc8448.cpp
@@ -1543,6 +1543,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Verify generated messages in server's first flight", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                const auto& msgs = ctx->observed_handshake_messages();
 
                result.test_eq("Server Hello", msgs.at("server_hello")[0], strip_message_header(vars.get_opt_bin("Message_ServerHello")));
@@ -1558,6 +1559,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Send Client Finished", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_ClientFinished"));
 
                ctx->check_callback_invocations(result, "client finished received", {
@@ -1568,6 +1570,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Send Session Ticket", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                const auto new_tickets = ctx->server.send_new_session_tickets(1);
 
                result.test_eq("session ticket was sent", new_tickets, 1);
@@ -1582,11 +1585,13 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Verify generated new session ticket message", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                result.test_eq("New Session Ticket", ctx->pull_send_buffer(), vars.get_req_bin("Record_NewSessionTicket"));
                }),
 
             Botan_Tests::CHECK("Receive Application Data", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_Client_AppData"));
                ctx->check_callback_invocations(result, "application data received", { "tls_record_received" });
 
@@ -1597,6 +1602,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Send Application Data", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                ctx->send(vars.get_req_bin("Server_AppData"));
 
                ctx->check_callback_invocations(result, "application data sent", { "tls_emit_data" });
@@ -1607,6 +1613,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Receive Client's close_notify", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_Client_CloseNotify"));
 
                ctx->check_callback_invocations(result, "client finished received", {
@@ -1620,6 +1627,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Expect Server close_notify", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.close();
 
                result.confirm("connection is now inactive", !ctx->server.is_active());
@@ -1672,6 +1680,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Verify generated messages in server's first flight", [&](Test::Result& result)
                {
+               result.require("ctx is available", ctx != nullptr);
                const auto& msgs = ctx->observed_handshake_messages();
 
                result.test_eq("Server Hello", msgs.at("server_hello")[0], strip_message_header(vars.get_opt_bin("Message_ServerHello")));
@@ -1731,14 +1740,14 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Verify generated Hello Retry Request message", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                result.test_eq("Server's Hello Retry Request record", ctx->pull_send_buffer(), vars.get_req_bin("Record_HelloRetryRequest"));
                result.confirm("TLS handshake not yet finished", !ctx->server.is_active());
                }),
 
             Botan_Tests::CHECK("Receive updated Client Hello message", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_ClientHello_2"));
 
                ctx->check_callback_invocations(result, "updated client hello received", {
@@ -1760,7 +1769,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Verify generated messages in server's second flight", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                const auto& msgs = ctx->observed_handshake_messages();
 
                result.test_eq("Server Hello", msgs.at("server_hello")[0], strip_message_header(vars.get_opt_bin("Message_ServerHello")));
@@ -1776,7 +1785,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Receive Client Finished", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_ClientFinished"));
 
                ctx->check_callback_invocations(result, "client finished received", {
@@ -1789,7 +1798,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Receive Client close_notify", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_Client_CloseNotify"));
 
                ctx->check_callback_invocations(result, "client finished received", {
@@ -1803,7 +1812,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Expect Server close_notify", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.close();
 
                result.confirm("connection is now inactive", !ctx->server.is_active());
@@ -1852,7 +1861,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Verify server's generated handshake messages", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                const auto& msgs = ctx->observed_handshake_messages();
 
                result.test_eq("Server Hello", msgs.at("server_hello")[0], strip_message_header(vars.get_opt_bin("Message_ServerHello")));
@@ -1871,7 +1880,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Receive Client's second flight", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                // This encrypted message contains the following messages:
                // * client's Certificate message
                // * client's Certificate_Verify message
@@ -1896,7 +1905,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Receive Client close_notify", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_Client_CloseNotify"));
 
                ctx->check_callback_invocations(result, "client finished received", {
@@ -1910,7 +1919,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Expect Server close_notify", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.close();
 
                result.confirm("connection is now inactive", !ctx->server.is_active());
@@ -1958,7 +1967,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Verify server's generated handshake messages", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                const auto& msgs = ctx->observed_handshake_messages();
 
                result.test_eq("Server Hello", msgs.at("server_hello")[0], strip_message_header(vars.get_opt_bin("Message_ServerHello")));
@@ -1976,7 +1985,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Receive Client Finished", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_ClientFinished"));
 
                ctx->check_callback_invocations(result, "client finished received", {
@@ -1989,7 +1998,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Receive Client close_notify", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.received_data(vars.get_req_bin("Record_Client_CloseNotify"));
 
                ctx->check_callback_invocations(result, "client finished received", {
@@ -2003,7 +2012,7 @@ class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
 
             Botan_Tests::CHECK("Expect Server close_notify", [&](Test::Result& result)
                {
-               BOTAN_ASSERT_NONNULL(ctx);
+               result.require("ctx is available", ctx != nullptr);
                ctx->server.close();
 
                result.confirm("connection is now inactive", !ctx->server.is_active());