Repository: randombit/botan
Pull request #3062 (2022-10-21 11:24:48)

[TLS 1.3] Refactor: Prepare for TLS 1.3 Server
### Pull Request Dependencies

~~This is based on https://github.com/randombit/botan/pull/3064~~

### Description

This applies preparatory refactorings to (hopefully) make the [initial pull request towards a TLS 1.3 server](https://github.com/randombit/botan/pull/3053) easier to review.

### Overview of Changes

#### Client Hello Marshalling

The wire-format of the client hello message did not change between TLS 1.2 and 1.3. I.e. we can reuse the parsing and serialization code. Similarly to the `Server_Hello` (already merged in TLS 1.3 client) this introduces a "dumb" `Client_Hello_Internal` class that encapsulates the marshalling.

`Client_Hello_13` gains a `::parse()` factory method that decides whether it deals with a 1.2 or 1.3 client hello and returns the appropriate type in a `std::variant<>`. The constructor of `Client_Hello_13` already contains some of the local sanity checks required in RFC 8446.

#### Removal of the `Server_Impl` Interface

This interface class was introduced way back by the Elektrobit folks. It turned out that it is not needed as all its defined methods are either TLS 1.2-specific or better defined in the generic `Channel_Impl` class. Similarly, we removed this `Client_Impl` counter-part during the TLS 1.3 client implementation.

#### `application_protocol()` vs. `next_protocol()`

To the best of my understanding, they have the same semantics. Hence, I removed the `next_protocol()` method from the lower-level code. The user-facing method (in `TLS::Server`) is now just a delegate to `application_protocol()` and is deprecated. Or should we remove it right away, @randombit?

#### Policy::choose_key_exchange_group()

This callback now has an additional parameter allowing applications to take the peer's offered groups into account. This way an application can define when to fall back to a Hello Retry Request even when mutually acceptable groups were already offered. One potential use case: A client might advertise support for a PQC exchange group but offered standard ECDH only. With this addition, the server could enforce PQ crypto at its own discretion.

#### `Signature_Algorithms` and `Signature_Algorithms_Cert` extensions

While we don't fully honor [the "signature_algorithms_cert" semantics](https://www.rfc-editor.org/rfc/rfc8446.html#section-4.2.3) just yet, we must be able to parse it. Both extensions share the same schema, hence this tries to unify their marshalling code better. See also https://github.com/randombit/botan/issues/2990.

#### Cipher State allows to receive data before ingesting the client Finished message

The server may start sending records encrypted under its "application traffic secret" even before receiving the client's Finished message. The `Cipher_State` must be able to handle this by deriving the server's traffic secrets ahead, while deferring the derivation of the client's secrets until after receiving the client's Finished. This was found via BoGo tests that expect to exchange a protected Alert record in this meta-situation.

#### `Server_Name_Indication` can be rendered by the Server

According to [RFC 6066](https://www.rfc-editor.org/rfc/rfc6066.html#page-13), the server may send an empty SNI extension as acknowledgement:

> Servers that receive a client hello containing the "trusted_ca_keys"
   extension MAY use the information contained in the extension to guide
   their selection of an appropriate certificate chain to return to the
   client.  In this event, the server SHALL include an extension of type
   "trusted_ca_keys" in the (extended) server hello.  The
   "extension_data" field of this extension SHALL be empty.

#### `generalize_to<>` Helper

This is a helper function that allows transforming a specific `std::variant<>` type (or individual concrete type) into a super-set. E.g.

```C++
using Pet = std::variant<Cat, Dog>;
using Animal = std::variant<Cat, Dog, Bat, Spider, Snake>;
Pet pet = Cat();
Cat cat;
auto animal = generalize_to<Animal>(pet);
auto other_animal = generalize_to<Animal>(cat);
```

#### Protocol Version Downgrade

Downgrading can now be explicitly requested by the concrete TLS client/server implementation class. Before, the Channel took a downgrade decision simply by reacting to a `Server_Hello_12`. This is not possible anymore, because TLS 1.3 servers must be able to downgrade even when receiving a `Client_Hello_13` (that explicitly negotiates TLS 1.2 in its "supported_versions" extension).

#### `tls_stream_integration` test

This is an integration test of the TLS asio adapter that sets up a test client and test server (as asio streams) and lets them talk to each other via a local TCP connection. We found some races and roadblocks when trying to extend this test with additional configurations using the TLS 1.3 server. This restructures the test's internal steering logic to better handle timeouts. Also, it adds some machinery to allow running the internal test cases with various TLS policies.

codecov-commenter: # [Codecov](https://codecov.io/gh/randombit/botan/pull/3062?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) Report
Base: **92.55**% // Head: **92.63**% // Increases project coverage by **`+0.07%`** :tada:
> Coverage data is based on head [(`9ad1a98`)](https://codecov.io/gh/randombit/botan/pull/3062?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) compared to base [(`864cbf2`)](https://codecov.io/gh/randombit/botan/commit/864cbf2760c862ed6ff1b15d9f046dcad4cbd6d0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd).
> Patch coverage: 91.34% of modified lines in pull request are covered.

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


```diff
@@            Coverage Diff             @@
##           master    #3062      +/-   ##
==========================================
+ Coverage   92.55%   92.63%   +0.07%     
==========================================
  Files         601      602       +1     
  Lines       70608    70667      +59     
  Branches     6666     6664       -2     
==========================================
+ Hits        65353    65462     +109     
+ Misses       5225     5175      -50     
  Partials       30       30              
```


| [Impacted Files](https://codecov.io/gh/randombit/botan/pull/3062?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd) | Coverage Δ | |
|---|---|---|
| [src/lib/tls/msg\_server\_hello.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvbXNnX3NlcnZlcl9oZWxsby5jcHA=) | `92.91% <ø> (-0.03%)` | :arrow_down: |
| [src/lib/tls/tls12/tls\_server\_impl\_12.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTIvdGxzX3NlcnZlcl9pbXBsXzEyLmNwcA==) | `88.41% <ø> (ø)` | |
| [src/lib/tls/tls13/tls\_handshake\_state\_13.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTMvdGxzX2hhbmRzaGFrZV9zdGF0ZV8xMy5jcHA=) | `90.90% <0.00%> (-9.10%)` | :arrow_down: |
| [src/lib/tls/tls\_server.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX3NlcnZlci5jcHA=) | `69.04% <64.28%> (+3.13%)` | :arrow_up: |
| [src/lib/tls/msg\_client\_hello.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvbXNnX2NsaWVudF9oZWxsby5jcHA=) | `93.17% <93.67%> (+0.45%)` | :arrow_up: |
| [src/lib/tls/tls\_extensions.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzX2V4dGVuc2lvbnMuY3Bw) | `90.45% <95.83%> (+0.84%)` | :arrow_up: |
| [src/lib/tls/tls13/tls\_handshake\_layer\_13.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL2xpYi90bHMvdGxzMTMvdGxzX2hhbmRzaGFrZV9sYXllcl8xMy5jcHA=) | `97.14% <100.00%> (ø)` | |
| [src/tests/test\_tls\_handshake\_layer\_13.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL3Rlc3RzL3Rlc3RfdGxzX2hhbmRzaGFrZV9sYXllcl8xMy5jcHA=) | `100.00% <100.00%> (ø)` | |
| [src/tests/test\_tls\_handshake\_state\_13.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL3Rlc3RzL3Rlc3RfdGxzX2hhbmRzaGFrZV9zdGF0ZV8xMy5jcHA=) | `100.00% <100.00%> (ø)` | |
| [src/tests/test\_tls\_messages.cpp](https://codecov.io/gh/randombit/botan/pull/3062/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Jack+Lloyd#diff-c3JjL3Rlc3RzL3Rlc3RfdGxzX21lc3NhZ2VzLmNwcA==) | `93.77% <100.00%> (+7.05%)` | :arrow_up: |
| ... and [13 more](https://codecov.io/gh/randombit/botan/pull/3062/diff?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/3062?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).

lgtm-com[bot]: This pull request **introduces 1 alert** when merging e9a3629ac9a03e9e7b4ef8125cb4b7076386323f into 76089a3e2452bf860fe3ecec86cf7406c53ed6f7 - [view on LGTM.com](https://lgtm.com/projects/g/randombit/botan/rev/pr-318599d41af94c4d43f17d417d0ea9200e921c19)

**new alerts:**

* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 1 alert** when merging e1d5c2c68d55d8be3d45e1108ba494a7723307e4 into 76089a3e2452bf860fe3ecec86cf7406c53ed6f7 - [view on LGTM.com](https://lgtm.com/projects/g/randombit/botan/rev/pr-2c2601789eca4afeb10c9ac2a3cc5fb16b5ad3cb)

**new alerts:**

* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 1 alert** when merging e65f2589f44d77dd87c58ea5de78d8cc31c47219 into 76089a3e2452bf860fe3ecec86cf7406c53ed6f7 - [view on LGTM.com](https://lgtm.com/projects/g/randombit/botan/rev/pr-8900a649b4de6a302940d8de0efada409bee6276)

**new alerts:**

* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 1 alert** when merging a65c80026aef574a8dd4cfd594047335277638f5 into 76089a3e2452bf860fe3ecec86cf7406c53ed6f7 - [view on LGTM.com](https://lgtm.com/projects/g/randombit/botan/rev/pr-fd7dc812d3dc06a95e91aa0cc78a5f78ab2856c8)

**new alerts:**

* 1 for Unused local variable
lgtm-com[bot]: This pull request **introduces 1 alert** when merging bdb0b8bb664fb7c9f7a7699e4c3b45faa7c0fb89 into 76089a3e2452bf860fe3ecec86cf7406c53ed6f7 - [view on LGTM.com](https://lgtm.com/projects/g/randombit/botan/rev/pr-e5bc630deee22f95c70877ac14b96ec17bb79b0a)

**new alerts:**

* 1 for Unused local variable

Patch:
diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp
index 5d23aa8e7..5c372816e 100644
--- a/src/bogo_shim/bogo_shim.cpp
+++ b/src/bogo_shim/bogo_shim.cpp
@@ -162,6 +162,9 @@ std::string map_to_bogo_error(const std::string& e)
          { "PSK and ciphersuite selected by server are not compatible", ":OLD_SESSION_PRF_HASH_MISMATCH:" },
          { "Received a record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:" },
          { "Received an encrypted record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:" },
+         { "received an illegal handshake message", ":UNEXPECTED_MESSAGE:" },
+         { "Received an unexpected legacy Client Hello", ":UNSUPPORTED_PROTOCOL:" },
+         { "Received an unexpected legacy Server Hello", ":UNSUPPORTED_PROTOCOL:" },
          { "Received application data after connection closure", ":APPLICATION_DATA_ON_SHUTDOWN:" },
          { "Received handshake data after connection closure", ":NO_RENEGOTIATION:" },
          { "Received unexpected record version in initial record", ":WRONG_VERSION_NUMBER:" },
diff --git a/src/lib/tls/info.txt b/src/lib/tls/info.txt
index 448701849..a62bd363c 100644
--- a/src/lib/tls/info.txt
+++ b/src/lib/tls/info.txt
@@ -33,7 +33,6 @@ tls_version.h
 tls_channel_impl.h
 tls_handshake_transitions.h
 tls_reader.h
-tls_server_impl.h
 </header:internal>
 
 <requires>
diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp
index 4ceb0afa9..05362904d 100644
--- a/src/lib/tls/msg_client_hello.cpp
+++ b/src/lib/tls/msg_client_hello.cpp
@@ -65,59 +65,116 @@ std::vector<uint8_t> make_hello_random(RandomNumberGenerator& rng,
    return buf;
    }
 
-/*
-* Read a counterparty client hello
-*/
-Client_Hello::Client_Hello(const std::vector<uint8_t>& buf)
+/**
+ * Version-agnostic internal client hello data container that allows
+ * parsing Client_Hello messages without prior knowledge of the contained
+ * protocol version.
+ */
+class Client_Hello_Internal
    {
-   if(buf.size() < 41)
-      { throw Decoding_Error("Client_Hello: Packet corrupted"); }
+   public:
+      Client_Hello_Internal() : comp_methods({0}) {}
 
-   TLS_Data_Reader reader("ClientHello", buf);
+      Client_Hello_Internal(const std::vector<uint8_t>& buf)
+         {
+         if(buf.size() < 41)
+            { throw Decoding_Error("Client_Hello: Packet corrupted"); }
 
-   const uint8_t major_version = reader.get_byte();
-   const uint8_t minor_version = reader.get_byte();
+         TLS_Data_Reader reader("ClientHello", buf);
 
-   m_legacy_version = Protocol_Version(major_version, minor_version);
-   m_random = reader.get_fixed<uint8_t>(32);
-   m_session_id = reader.get_range<uint8_t>(1, 0, 32);
+         const uint8_t major_version = reader.get_byte();
+         const uint8_t minor_version = reader.get_byte();
 
-   if(m_legacy_version.is_datagram_protocol())
-      {
-      auto sha256 = HashFunction::create_or_throw("SHA-256");
-      sha256->update(reader.get_data_read_so_far());
+         legacy_version = Protocol_Version(major_version, minor_version);
+         random = reader.get_fixed<uint8_t>(32);
+         session_id = reader.get_range<uint8_t>(1, 0, 32);
 
-      m_hello_cookie = reader.get_range<uint8_t>(1, 0, 255);
+         if(legacy_version.is_datagram_protocol())
+            {
+            auto sha256 = HashFunction::create_or_throw("SHA-256");
+            sha256->update(reader.get_data_read_so_far());
 
-      sha256->update(reader.get_remaining());
-      m_cookie_input_bits.resize(sha256->output_length());
-      sha256->final(m_cookie_input_bits.data());
-      }
-
-   m_suites = reader.get_range_vector<uint16_t>(2, 1, 32767);
+            hello_cookie = reader.get_range<uint8_t>(1, 0, 255);
 
-   m_comp_methods = reader.get_range_vector<uint8_t>(1, 1, 255);
+            sha256->update(reader.get_remaining());
+            cookie_input_bits = sha256->final_stdvec();
+            }
 
-   m_extensions.deserialize(reader, Connection_Side::CLIENT, type());
+         suites = reader.get_range_vector<uint16_t>(2, 1, 32767);
+         comp_methods = reader.get_range_vector<uint8_t>(1, 1, 255);
 
-   // TODO: Reject oid_filters extension if found (which is the only known extension that
-   //       must not occur in the TLS 1.3 client hello.
-   // RFC 8446 4.2.5
-   //    [The oid_filters extension] MUST only be sent in the CertificateRequest message.
-   if(offered_suite(static_cast<uint16_t>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV)))
-      {
-      if(Renegotiation_Extension* reneg = m_extensions.get<Renegotiation_Extension>())
-         {
-         if(!reneg->renegotiation_info().empty())
-            throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
-                                "Client sent renegotiation SCSV and non-empty extension");
+         extensions.deserialize(reader, Connection_Side::CLIENT, Handshake_Type::CLIENT_HELLO);
          }
-      else
+
+      /**
+       * This distinguishes between a TLS 1.3 compliant Client Hello (containing
+       * the "supported_version" extension) and legacy Client Hello messages.
+       *
+       * @return TLS 1.3 if the Client Hello contains "supported_versions", or
+       *         the content of the "legacy_version" version field if it
+       *         indicates (D)TLS 1.2 or older, or
+       *         (D)TLS 1.2 if the "legacy_version" was some other odd value.
+       */
+      Protocol_Version version() const
          {
-         // add fake extension
-         m_extensions.add(new Renegotiation_Extension());
+         // RFC 8446 4.2.1
+         //    If [the "supported_versions"] extension is not present, servers
+         //    which are compliant with this specification and which also support
+         //    TLS 1.2 MUST negotiate TLS 1.2 or prior as specified in [RFC5246],
+         //    even if ClientHello.legacy_version is 0x0304 or later.
+         //
+         // RFC 8446 4.2.1
+         //    Servers MUST be prepared to receive ClientHellos that include
+         //    [the supported_versions] extension but do not include 0x0304 in
+         //    the list of versions.
+         //
+         // RFC 8446 4.1.2
+         //    TLS 1.3 ClientHellos are identified as having a legacy_version of
+         //    0x0303 and a supported_versions extension present with 0x0304 as
+         //    the highest version indicated therein.
+         if(!extensions.has<Supported_Versions>() ||
+            !extensions.get<Supported_Versions>()->supports(Protocol_Version::TLS_V13))
+            {
+            // The exact legacy_version is ignored we just inspect it to
+            // distinguish TLS and DTLS.
+            return (legacy_version.is_datagram_protocol())
+               ? Protocol_Version::DTLS_V12
+               : Protocol_Version::TLS_V12;
+            }
+
+         // Note: The Client_Hello_13 class will make sure that legacy_version
+         //       is exactly 0x0303 (aka ossified TLS 1.2)
+         return Protocol_Version::TLS_V13;
          }
-      }
+
+   public:
+      Protocol_Version legacy_version;
+      std::vector<uint8_t> session_id;
+      std::vector<uint8_t> random;
+      std::vector<uint16_t> suites;
+      std::vector<uint8_t> comp_methods;
+      Extensions extensions;
+
+      std::vector<uint8_t> hello_cookie; // DTLS only
+      std::vector<uint8_t> cookie_input_bits; // DTLS only
+   };
+
+
+Client_Hello::Client_Hello(Client_Hello&&) = default;
+Client_Hello& Client_Hello::operator=(Client_Hello&&) = default;
+
+Client_Hello::~Client_Hello() = default;
+
+Client_Hello::Client_Hello()
+   : m_data(std::make_unique<Client_Hello_Internal>()) {}
+
+/*
+* Read a counterparty client hello
+*/
+Client_Hello::Client_Hello(std::unique_ptr<Client_Hello_Internal> data)
+   : m_data(std::move(data))
+   {
+   BOTAN_ASSERT_NONNULL(m_data);
    }
 
 Handshake_Type Client_Hello::type() const
@@ -127,45 +184,44 @@ Handshake_Type Client_Hello::type() const
 
 Protocol_Version Client_Hello::legacy_version() const
    {
-   return m_legacy_version;
+   return m_data->legacy_version;
    }
 
 const std::vector<uint8_t>& Client_Hello::random() const
    {
-   return m_random;
+   return m_data->random;
    }
 
 const std::vector<uint8_t>& Client_Hello::session_id() const
    {
-   return m_session_id;
+   return m_data->session_id;
    }
 
 const std::vector<uint8_t>& Client_Hello::compression_methods() const
    {
-   return m_comp_methods;
+   return m_data->comp_methods;
    }
 
 const std::vector<uint16_t>& Client_Hello::ciphersuites() const
    {
-   return m_suites;
+   return m_data->suites;
    }
 
 std::set<Handshake_Extension_Type> Client_Hello::extension_types() const
    {
-   return m_extensions.extension_types();
+   return m_data->extensions.extension_types();
    }
 
 const Extensions& Client_Hello::extensions() const
    {
-   return m_extensions;
+   return m_data->extensions;
    }
 
 void Client_Hello_12::update_hello_cookie(const Hello_Verify_Request& hello_verify)
    {
-   if(!m_legacy_version.is_datagram_protocol())
-      { throw Invalid_State("Cannot use hello cookie with stream protocol"); }
+   BOTAN_STATE_CHECK(m_data->legacy_version.is_datagram_protocol());
 
-   m_hello_cookie = hello_verify.cookie();
+   m_data->hello_cookie = hello_verify.cookie();
    }
 
 /*
@@ -176,17 +232,17 @@ std::vector<uint8_t> Client_Hello::serialize() const
    std::vector<uint8_t> buf;
    buf.reserve(1024); // working around GCC warning
 
-   buf.push_back(m_legacy_version.major_version());
-   buf.push_back(m_legacy_version.minor_version());
-   buf += m_random;
+   buf.push_back(m_data->legacy_version.major_version());
+   buf.push_back(m_data->legacy_version.minor_version());
+   buf += m_data->random;
 
-   append_tls_length_value(buf, m_session_id, 1);
+   append_tls_length_value(buf, m_data->session_id, 1);
 
-   if(m_legacy_version.is_datagram_protocol())
-      { append_tls_length_value(buf, m_hello_cookie, 1); }
+   if(m_data->legacy_version.is_datagram_protocol())
+      { append_tls_length_value(buf, m_data->hello_cookie, 1); }
 
-   append_tls_length_value(buf, m_suites, 2);
-   append_tls_length_value(buf, m_comp_methods, 1);
+   append_tls_length_value(buf, m_data->suites, 2);
+   append_tls_length_value(buf, m_data->comp_methods, 1);
 
    /*
    * May not want to send extensions at all in some cases. If so,
@@ -194,17 +250,16 @@ std::vector<uint8_t> Client_Hello::serialize() const
    * renegotiating with a modern server)
    */
 
-   buf += m_extensions.serialize(Connection_Side::CLIENT);
+   buf += m_data->extensions.serialize(Connection_Side::CLIENT);
 
    return buf;
    }
 
 std::vector<uint8_t> Client_Hello::cookie_input_data() const
    {
-   if(m_cookie_input_bits.empty())
-      { throw Invalid_State("Client_Hello::cookie_input_data called but was not computed"); }
+   BOTAN_STATE_CHECK(!m_data->cookie_input_bits.empty());
 
-   return m_cookie_input_bits;
+   return m_data->cookie_input_bits;
    }
 
 /*
@@ -212,125 +267,119 @@ std::vector<uint8_t> Client_Hello::cookie_input_data() const
 */
 bool Client_Hello::offered_suite(uint16_t ciphersuite) const
    {
-   return std::find(m_suites.cbegin(), m_suites.cend(), ciphersuite) != m_suites.cend();
+   return std::find(m_data->suites.cbegin(), m_data->suites.cend(), ciphersuite) != m_data->suites.cend();
    }
 
 std::vector<Signature_Scheme> Client_Hello::signature_schemes() const
    {
-   std::vector<Signature_Scheme> schemes;
-
-   if(Signature_Algorithms* sigs = m_extensions.get<Signature_Algorithms>())
-      {
-      schemes = sigs->supported_schemes();
-      }
-
-   return schemes;
+   if(Signature_Algorithms* sigs = m_data->extensions.get<Signature_Algorithms>())
+      { return sigs->supported_schemes(); }
+   return {};
    }
 
 std::vector<Group_Params> Client_Hello::supported_ecc_curves() const
    {
-   if(Supported_Groups* groups = m_extensions.get<Supported_Groups>())
+   if(Supported_Groups* groups = m_data->extensions.get<Supported_Groups>())
       { return groups->ec_groups(); }
-   return std::vector<Group_Params>();
+   return {};
    }
 
 std::vector<Group_Params> Client_Hello::supported_dh_groups() const
    {
-   if(Supported_Groups* groups = m_extensions.get<Supported_Groups>())
+   if(Supported_Groups* groups = m_data->extensions.get<Supported_Groups>())
       { return groups->dh_groups(); }
    return std::vector<Group_Params>();
    }
 
 bool Client_Hello_12::prefers_compressed_ec_points() const
    {
-   if(Supported_Point_Formats* ecc_formats = m_extensions.get<Supported_Point_Formats>())
-      {
-      return ecc_formats->prefers_compressed();
-      }
+   if(Supported_Point_Formats* ecc_formats = m_data->extensions.get<Supported_Point_Formats>())
+      { return ecc_formats->prefers_compressed(); }
    return false;
    }
 
 std::string Client_Hello::sni_hostname() const
    {
-   if(Server_Name_Indicator* sni = m_extensions.get<Server_Name_Indicator>())
+   if(Server_Name_Indicator* sni = m_data->extensions.get<Server_Name_Indicator>())
       { return sni->host_name(); }
    return "";
    }
 
 bool Client_Hello_12::secure_renegotiation() const
    {
-   return m_extensions.has<Renegotiation_Extension>();
+   return m_data->extensions.has<Renegotiation_Extension>();
    }
 
 std::vector<uint8_t> Client_Hello_12::renegotiation_info() const
    {
-   if(Renegotiation_Extension* reneg = m_extensions.get<Renegotiation_Extension>())
+   if(Renegotiation_Extension* reneg = m_data->extensions.get<Renegotiation_Extension>())
       { return reneg->renegotiation_info(); }
-   return std::vector<uint8_t>();
+   return {};
    }
 
 std::vector<Protocol_Version> Client_Hello::supported_versions() const
    {
-   if(Supported_Versions* versions = m_extensions.get<Supported_Versions>())
+   if(Supported_Versions* versions = m_data->extensions.get<Supported_Versions>())
       { return versions->versions(); }
    return {};
    }
 
 bool Client_Hello_12::supports_session_ticket() const
    {
-   return m_extensions.has<Session_Ticket>();
+   return m_data->extensions.has<Session_Ticket>();
    }
 
 std::vector<uint8_t> Client_Hello_12::session_ticket() const
    {
-   if(Session_Ticket* ticket = m_extensions.get<Session_Ticket>())
+   if(Session_Ticket* ticket = m_data->extensions.get<Session_Ticket>())
       { return ticket->contents(); }
-   return std::vector<uint8_t>();
+   return {};
    }
 
 bool Client_Hello::supports_alpn() const
    {
-   return m_extensions.has<Application_Layer_Protocol_Notification>();
+   return m_data->extensions.has<Application_Layer_Protocol_Notification>();
    }
 
 bool Client_Hello_12::supports_extended_master_secret() const
    {
-   return m_extensions.has<Extended_Master_Secret>();
+   return m_data->extensions.has<Extended_Master_Secret>();
    }
 
 bool Client_Hello_12::supports_cert_status_message() const
    {
-   return m_extensions.has<Certificate_Status_Request>();
+   return m_data->extensions.has<Certificate_Status_Request>();
    }
 
 bool Client_Hello_12::supports_encrypt_then_mac() const
    {
-   return m_extensions.has<Encrypt_then_MAC>();
+   return m_data->extensions.has<Encrypt_then_MAC>();
    }
 
 bool Client_Hello::sent_signature_algorithms() const
    {
-   return m_extensions.has<Signature_Algorithms>();
+   return m_data->extensions.has<Signature_Algorithms>();
    }
 
 std::vector<std::string> Client_Hello::next_protocols() const
    {
-   if(auto alpn = m_extensions.get<Application_Layer_Protocol_Notification>())
+   if(auto alpn = m_data->extensions.get<Application_Layer_Protocol_Notification>())
       { return alpn->protocols(); }
-   return std::vector<std::string>();
+   return {};
    }
 
 std::vector<uint16_t> Client_Hello::srtp_profiles() const
    {
-   if(SRTP_Protection_Profiles* srtp = m_extensions.get<SRTP_Protection_Profiles>())
+   if(SRTP_Protection_Profiles* srtp = m_data->extensions.get<SRTP_Protection_Profiles>())
       { return srtp->profiles(); }
-   return std::vector<uint16_t>();
+   return {};
    }
 
 const std::vector<uint8_t>& Client_Hello::cookie() const
    {
-   return m_hello_cookie;
+   return m_data->hello_cookie;
    }
+
 /*
 * Create a new Hello Request message
 */
@@ -356,6 +405,31 @@ std::vector<uint8_t> Hello_Request::serialize() const
    return std::vector<uint8_t>();
    }
 
+
+Client_Hello_12::Client_Hello_12(std::unique_ptr<Client_Hello_Internal> data)
+   : Client_Hello(std::move(data))
+   {
+   if(offered_suite(static_cast<uint16_t>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV)))
+      {
+      if(Renegotiation_Extension* reneg = m_data->extensions.get<Renegotiation_Extension>())
+         {
+         if(!reneg->renegotiation_info().empty())
+            throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+                                "Client sent renegotiation SCSV and non-empty extension");
+         }
+      else
+         {
+         // add fake extension
+         m_data->extensions.add(new Renegotiation_Extension());
+         }
+      }
+   }
+
+// Note: This delegates to the Client_Hello_12 constructor to take advantage
+//       of the sanity checks there.
+Client_Hello_12::Client_Hello_12(const std::vector<uint8_t>& buf)
+   : Client_Hello_12(std::make_unique<Client_Hello_Internal>(buf)) {}
+
 /*
 * Create a new Client Hello message
 */
@@ -368,12 +442,12 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
                                  const Client_Hello_12::Settings& client_settings,
                                  const std::vector<std::string>& next_protocols)
    {
-   m_legacy_version = client_settings.protocol_version();
-   m_random = make_hello_random(rng, cb, policy);
-   m_suites = policy.ciphersuite_list(client_settings.protocol_version());
+   m_data->legacy_version = client_settings.protocol_version();
+   m_data->random = make_hello_random(rng, cb, policy);
+   m_data->suites = policy.ciphersuite_list(client_settings.protocol_version());
 
-   if(!policy.acceptable_protocol_version(m_legacy_version))
-      throw Internal_Error("Offering " + m_legacy_version.to_string() +
+   if(!policy.acceptable_protocol_version(m_data->legacy_version))
+      throw Internal_Error("Offering " + m_data->legacy_version.to_string() +
                            " but our own policy does not accept it");
 
    /*
@@ -382,39 +456,39 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
    */
 
    // EMS must always be used with TLS 1.2, regardless of the policy used.
-   m_extensions.add(new Extended_Master_Secret);
+   m_data->extensions.add(new Extended_Master_Secret);
 
    if(policy.negotiate_encrypt_then_mac())
-      { m_extensions.add(new Encrypt_then_MAC); }
+      { m_data->extensions.add(new Encrypt_then_MAC); }
 
-   m_extensions.add(new Session_Ticket());
+   m_data->extensions.add(new Session_Ticket());
 
-   m_extensions.add(new Renegotiation_Extension(reneg_info));
+   m_data->extensions.add(new Renegotiation_Extension(reneg_info));
 
-   m_extensions.add(new Supported_Versions(m_legacy_version, policy));
+   m_data->extensions.add(new Supported_Versions(m_data->legacy_version, policy));
 
    if(!client_settings.hostname().empty())
-      { m_extensions.add(new Server_Name_Indicator(client_settings.hostname())); }
+      { m_data->extensions.add(new Server_Name_Indicator(client_settings.hostname())); }
 
    if(policy.support_cert_status_message())
-      m_extensions.add(new Certificate_Status_Request({}, {}));
+      m_data->extensions.add(new Certificate_Status_Request({}, {}));
 
    auto supported_groups = std::make_unique<Supported_Groups>(policy.key_exchange_groups());
    if(!supported_groups->ec_groups().empty())
       {
-      m_extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
+      m_data->extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
       }
-   m_extensions.add(supported_groups.release());
+   m_data->extensions.add(supported_groups.release());
 
-   m_extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
+   m_data->extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
 
    if(reneg_info.empty() && !next_protocols.empty())
-      { m_extensions.add(new Application_Layer_Protocol_Notification(next_protocols)); }
+      { m_data->extensions.add(new Application_Layer_Protocol_Notification(next_protocols)); }
 
-   if(m_legacy_version.is_datagram_protocol())
-      { m_extensions.add(new SRTP_Protection_Profiles(policy.srtp_profiles())); }
+   if(m_data->legacy_version.is_datagram_protocol())
+      { m_data->extensions.add(new SRTP_Protection_Profiles(policy.srtp_profiles())); }
 
-   cb.tls_modify_extensions(m_extensions, CLIENT, type());
+   cb.tls_modify_extensions(m_data->extensions, CLIENT, type());
 
    hash.update(io.send(*this));
    }
@@ -431,17 +505,17 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
                                  const Session& session,
                                  const std::vector<std::string>& next_protocols)
    {
-   m_legacy_version = session.version();
-   m_random = make_hello_random(rng, cb, policy);
-   m_session_id = session.session_id();
-   m_suites = policy.ciphersuite_list(m_legacy_version);
+   m_data->legacy_version = session.version();
+   m_data->random = make_hello_random(rng, cb, policy);
+   m_data->session_id = session.session_id();
+   m_data->suites = policy.ciphersuite_list(m_data->legacy_version);
 
    if(!policy.acceptable_protocol_version(session.version()))
-      throw Internal_Error("Offering " + m_legacy_version.to_string() +
+      throw Internal_Error("Offering " + m_data->legacy_version.to_string() +
                            " but our own policy does not accept it");
 
-   if(!value_exists(m_suites, session.ciphersuite_code()))
-      { m_suites.push_back(session.ciphersuite_code()); }
+   if(!value_exists(m_data->suites, session.ciphersuite_code()))
+      { m_data->suites.push_back(session.ciphersuite_code()); }
 
    /*
    * As EMS must always be used with TLS 1.2, add it even if it wasn't used
@@ -449,41 +523,80 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
    * RFC it should reject our resume attempt and upgrade us to a new session
    * with the EMS protection.
    */
-   m_extensions.add(new Extended_Master_Secret);
+   m_data->extensions.add(new Extended_Master_Secret);
 
    if(session.supports_encrypt_then_mac())
-      { m_extensions.add(new Encrypt_then_MAC); }
+      { m_data->extensions.add(new Encrypt_then_MAC); }
 
-   m_extensions.add(new Session_Ticket(session.session_ticket()));
+   m_data->extensions.add(new Session_Ticket(session.session_ticket()));
 
-   m_extensions.add(new Renegotiation_Extension(reneg_info));
+   m_data->extensions.add(new Renegotiation_Extension(reneg_info));
 
-   m_extensions.add(new Server_Name_Indicator(session.server_info().hostname()));
+   m_data->extensions.add(new Server_Name_Indicator(session.server_info().hostname()));
 
    if(policy.support_cert_status_message())
-      m_extensions.add(new Certificate_Status_Request({}, {}));
+      m_data->extensions.add(new Certificate_Status_Request({}, {}));
 
    auto supported_groups = std::make_unique<Supported_Groups>(policy.key_exchange_groups());
 
    if(!supported_groups->ec_groups().empty())
       {
-      m_extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
+      m_data->extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
       }
 
-   m_extensions.add(supported_groups.release());
+   m_data->extensions.add(supported_groups.release());
 
-   m_extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
+   m_data->extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
 
    if(reneg_info.empty() && !next_protocols.empty())
-      { m_extensions.add(new Application_Layer_Protocol_Notification(next_protocols)); }
+      { m_data->extensions.add(new Application_Layer_Protocol_Notification(next_protocols)); }
 
-   cb.tls_modify_extensions(m_extensions, CLIENT, type());
+   cb.tls_modify_extensions(m_data->extensions, CLIENT, type());
 
    hash.update(io.send(*this));
    }
 
 #if defined(BOTAN_HAS_TLS_13)
 
+Client_Hello_13::Client_Hello_13(std::unique_ptr<Client_Hello_Internal> data)
+   : Client_Hello(std::move(data))
+   {
+   // RFC 8446 4.1.2
+   //    TLS 1.3 ClientHellos are identified as having a legacy_version of
+   //    0x0303 and a "supported_versions" extension present with 0x0304 as the
+   //    highest version indicated therein.
+   //
+   // Note that we already checked for "supported_versions" before entering this
+   // c'tor in `Client_Hello_13::parse()`. This is just to be doubly sure.
+   BOTAN_ASSERT_NOMSG(m_data->extensions.has<Supported_Versions>());
+
+   // RFC 8446 4.2.1
+   //    Servers MAY abort the handshake upon receiving a ClientHello with
+   //    legacy_version 0x0304 or later.
+   if(m_data->legacy_version != Protocol_Version::TLS_V12)
+      {
+      throw TLS_Exception(Alert::DECODE_ERROR,
+                          "TLS 1.3 Client Hello has invalid legacy_version");
+      }
+
+   // RFC 8446 4.1.2
+   //    For every TLS 1.3 ClientHello, [the compression method] MUST contain
+   //    exactly one byte, set to zero, [...].  If a TLS 1.3 ClientHello is
+   //    received with any other value in this field, the server MUST abort the
+   //    handshake with an "illegal_parameter" alert.
+   if(m_data->comp_methods.size() != 1 || m_data->comp_methods.front() != 0)
+      {
+      throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
+                          "Client sent TLS 1.3 Client Hello with compression "
+                          "method != null");
+      }
+
+   // TODO: Reject oid_filters extension if found (which is the only known extension that
+   //       must not occur in the TLS 1.3 client hello.
+   // RFC 8446 4.2.5
+   //    [The oid_filters extension] MUST only be sent in the CertificateRequest message.
+   }
+
 /*
 * Create a new Client Hello message
 */
@@ -499,14 +612,14 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
    //    "supported_versions" extension (Section 4.2.1) and the
    //    legacy_version field MUST be set to 0x0303, which is the version
    //    number for TLS 1.2.
-   m_legacy_version = Protocol_Version::TLS_V12;
-   m_random = make_hello_random(rng, cb, policy);
-   m_suites = policy.ciphersuite_list(Protocol_Version::TLS_V13);
+   m_data->legacy_version = Protocol_Version::TLS_V12;
+   m_data->random = make_hello_random(rng, cb, policy);
+   m_data->suites = policy.ciphersuite_list(Protocol_Version::TLS_V13);
 
    if(policy.allow_tls12())  // Note: DTLS 1.3 is NYI, hence dtls_12 is not checked
       {
       const auto legacy_suites = policy.ciphersuite_list(Protocol_Version::TLS_V12);
-      m_suites.insert(m_suites.end(), legacy_suites.cbegin(), legacy_suites.cend());
+      m_data->suites.insert(m_data->suites.end(), legacy_suites.cbegin(), legacy_suites.cend());
       }
 
    if(policy.tls_13_middlebox_compatibility_mode())
@@ -515,59 +628,59 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
       //    In compatibility mode (see Appendix D.4), this field MUST be non-empty,
       //    so a client not offering a pre-TLS 1.3 session MUST generate a new
       //    32-byte value.
-      rng.random_vec(m_session_id, 32);
+      rng.random_vec(m_data->session_id, 32);
       }
 
    if(!hostname.empty())
-      m_extensions.add(new Server_Name_Indicator(hostname));
+      m_data->extensions.add(new Server_Name_Indicator(hostname));
 
-   m_extensions.add(new Supported_Groups(policy.key_exchange_groups()));
+   m_data->extensions.add(new Supported_Groups(policy.key_exchange_groups()));
 
-   m_extensions.add(new Key_Share(policy, cb, rng));
+   m_data->extensions.add(new Key_Share(policy, cb, rng));
 
-   m_extensions.add(new Supported_Versions(Protocol_Version::TLS_V13, policy));
+   m_data->extensions.add(new Supported_Versions(Protocol_Version::TLS_V13, policy));
 
-   m_extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
+   m_data->extensions.add(new Signature_Algorithms(policy.acceptable_signature_schemes()));
 
    // TODO: Support for PSK-only mode without a key exchange.
    //       This should be configurable in TLS::Policy and should allow no PSK
    //       support at all (e.g. to disable support for session resumption).
-   m_extensions.add(new PSK_Key_Exchange_Modes({PSK_Key_Exchange_Mode::PSK_DHE_KE}));
+   m_data->extensions.add(new PSK_Key_Exchange_Modes({PSK_Key_Exchange_Mode::PSK_DHE_KE}));
 
    // TODO: Add a signature_algorithms_cert extension negotiating the acceptable
    //       signature algorithms in a server certificate chain's certificates.
 
    if(policy.support_cert_status_message())
-      m_extensions.add(new Certificate_Status_Request({}, {}));
+      m_data->extensions.add(new Certificate_Status_Request({}, {}));
 
    // We currently support "record_size_limit" for TLS 1.3 exclusively. Hence,
    // when TLS 1.2 is advertised as a supported protocol, we must not offer this
    // extension.
    if(policy.record_size_limit().has_value() && !policy.allow_tls12())
-      m_extensions.add(new Record_Size_Limit(policy.record_size_limit().value()));
+      m_data->extensions.add(new Record_Size_Limit(policy.record_size_limit().value()));
 
    if(!next_protocols.empty())
-      m_extensions.add(new Application_Layer_Protocol_Notification(next_protocols));
+      m_data->extensions.add(new Application_Layer_Protocol_Notification(next_protocols));
 
    if(policy.allow_tls12())
       {
-      m_extensions.add(new Renegotiation_Extension());
-      m_extensions.add(new Session_Ticket());
+      m_data->extensions.add(new Renegotiation_Extension());
+      m_data->extensions.add(new Session_Ticket());
 
       // EMS must always be used with TLS 1.2, regardless of the policy
-      m_extensions.add(new Extended_Master_Secret);
+      m_data->extensions.add(new Extended_Master_Secret);
 
       if(policy.negotiate_encrypt_then_mac())
-         m_extensions.add(new Encrypt_then_MAC);
+         m_data->extensions.add(new Encrypt_then_MAC);
 
-      if(m_extensions.has<Supported_Groups>() && !m_extensions.get<Supported_Groups>()->ec_groups().empty())
-         m_extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
+      if(m_data->extensions.has<Supported_Groups>() && !m_data->extensions.get<Supported_Groups>()->ec_groups().empty())
+         m_data->extensions.add(new Supported_Point_Formats(policy.use_ecc_point_compression()));
       }
 
    // TODO: Some extensions require a certain order or pose other assumptions.
    //       We should check those after the user was allowed to make changes to
    //       the extensions.
-   cb.tls_modify_extensions(m_extensions, CLIENT, type());
+   cb.tls_modify_extensions(m_data->extensions, CLIENT, type());
 
    // RFC 8446 4.2.11
    //    The "pre_shared_key" extension MUST be the last extension in the
@@ -577,24 +690,37 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
    // into Callbacks::tls_modify_extensions() does not make sense therefore.
    if(session.has_value())
       {
-      m_extensions.add(new PSK(session.value(), cb));
+      m_data->extensions.add(new PSK(session.value(), cb));
       calculate_psk_binders({});
       }
    }
 
+
+std::variant<Client_Hello_13, Client_Hello_12>
+Client_Hello_13::parse(const std::vector<uint8_t>& buf)
+   {
+   auto data = std::make_unique<Client_Hello_Internal>(buf);
+   const auto version = data->version();
+
+   if(version.is_pre_tls_13())
+      return Client_Hello_12(std::move(data));
+   else
+      return Client_Hello_13(std::move(data));
+   }
+
 void Client_Hello_13::retry(const Hello_Retry_Request& hrr,
                             const Transcript_Hash_State& transcript_hash_state,
                             Callbacks& cb,
                             RandomNumberGenerator& rng)
    {
-   BOTAN_STATE_CHECK(m_extensions.has<Supported_Groups>());
-   BOTAN_STATE_CHECK(m_extensions.has<Key_Share>());
+   BOTAN_STATE_CHECK(m_data->extensions.has<Supported_Groups>());
+   BOTAN_STATE_CHECK(m_data->extensions.has<Key_Share>());
 
    auto hrr_ks = hrr.extensions().get<Key_Share>();
-   const auto& supported_groups = m_extensions.get<Supported_Groups>()->groups();
+   const auto& supported_groups = m_data->extensions.get<Supported_Groups>()->groups();
 
    if(hrr.extensions().has<Key_Share>())
-      m_extensions.get<Key_Share>()->retry_offer(*hrr_ks, supported_groups, cb, rng);
+      m_data->extensions.get<Key_Share>()->retry_offer(*hrr_ks, supported_groups, cb, rng);
 
    // RFC 8446 4.2.2
    //    When sending the new ClientHello, the client MUST copy
@@ -606,17 +732,17 @@ void Client_Hello_13::retry(const Hello_Retry_Request& hrr,
    //    connections.
    if(hrr.extensions().has<Cookie>())
       {
-      BOTAN_STATE_CHECK(!m_extensions.has<Cookie>());
-      m_extensions.add(new Cookie(hrr.extensions().get<Cookie>()->get_cookie()));
+      BOTAN_STATE_CHECK(!m_data->extensions.has<Cookie>());
+      m_data->extensions.add(new Cookie(hrr.extensions().get<Cookie>()->get_cookie()));
       }
 
    // Note: the consumer of the TLS implementation won't be able to distinguish
    //       invocations to this callback due to the first Client_Hello or the
    //       retried Client_Hello after receiving a Hello_Retry_Request. We assume
    //       that the user keeps and detects this state themselves.
-   cb.tls_modify_extensions(m_extensions, CLIENT, type());
+   cb.tls_modify_extensions(m_data->extensions, CLIENT, type());
 
-   auto psk = m_extensions.get<PSK>();
+   auto psk = m_data->extensions.get<PSK>();
    if(psk)
       {
       // Cipher suite should always be a known suite as this is checked upstream
@@ -638,7 +764,7 @@ void Client_Hello_13::retry(const Hello_Retry_Request& hrr,
 
 void Client_Hello_13::calculate_psk_binders(Transcript_Hash_State ths)
    {
-   auto psk = m_extensions.get<PSK>();
+   auto psk = m_data->extensions.get<PSK>();
    if(!psk || psk->empty())
       return;
 
diff --git a/src/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp
index 397203018..a3b51c438 100644
--- a/src/lib/tls/msg_server_hello.cpp
+++ b/src/lib/tls/msg_server_hello.cpp
@@ -481,8 +481,6 @@ Server_Hello_13::Hello_Retry_Request_Tag Server_Hello_13::as_hello_retry_request
 std::variant<Hello_Retry_Request, Server_Hello_13, Server_Hello_12>
 Server_Hello_13::parse(const std::vector<uint8_t>& buf)
    {
-   TLS_Data_Reader reader("Server_Hello_13::parse", buf);
-
    auto data = std::make_unique<Server_Hello_Internal>(buf);
    const auto version = data->version();
 
diff --git a/src/lib/tls/tls12/tls_channel_impl_12.h b/src/lib/tls/tls12/tls_channel_impl_12.h
index 040d55d17..4e1f01fd8 100644
--- a/src/lib/tls/tls12/tls_channel_impl_12.h
+++ b/src/lib/tls/tls12/tls_channel_impl_12.h
@@ -99,6 +99,9 @@ class Channel_Impl_12 : public Channel_Impl
       */
       bool is_closed() const override;
 
+      bool is_closed_for_reading() const override { return is_closed(); }
+      bool is_closed_for_writing() const override { return is_closed(); }
+
       /**
       * @return certificate chain of the peer (may be empty)
       */
diff --git a/src/lib/tls/tls12/tls_client_impl_12.cpp b/src/lib/tls/tls12/tls_client_impl_12.cpp
index d8ed2f823..a18c5701d 100644
--- a/src/lib/tls/tls12/tls_client_impl_12.cpp
+++ b/src/lib/tls/tls12/tls_client_impl_12.cpp
@@ -83,7 +83,8 @@ Client_Impl_12::Client_Impl_12(const Channel_Impl::Downgrade_Information& downgr
                    downgrade_info.rng,
                    downgrade_info.policy,
                    false /* is_server */,
-                   false /* datagram -- not supported by Botan in TLS 1.3 */),
+                   false /* datagram -- not supported by Botan in TLS 1.3 */,
+                   downgrade_info.io_buffer_size),
    m_creds(downgrade_info.creds),
    m_info(downgrade_info.server_info)
    {
diff --git a/src/lib/tls/tls12/tls_server_impl_12.cpp b/src/lib/tls/tls12/tls_server_impl_12.cpp
index a8ea5bf76..3e0abf397 100644
--- a/src/lib/tls/tls12/tls_server_impl_12.cpp
+++ b/src/lib/tls/tls12/tls_server_impl_12.cpp
@@ -271,7 +271,6 @@ Server_Impl_12::Server_Impl_12(Callbacks& callbacks,
                                size_t io_buf_sz) :
    Channel_Impl_12(callbacks, session_manager, rng, policy,
                   true, is_datagram, io_buf_sz),
-   Server_Impl(static_cast<Channel_Impl&>(*this)),
    m_creds(creds)
    {
    }
@@ -508,12 +507,6 @@ void Server_Impl_12::process_client_hello_msg(const Handshake_State* active_stat
    if(pending_state.client_hello()->supports_alpn())
       {
       m_next_protocol = callbacks().tls_server_choose_app_protocol(pending_state.client_hello()->next_protocols());
-
-      // if the callback return was empty, fall back to the (deprecated) std::function
-      if(m_next_protocol.empty() && m_choose_next_protocol)
-         {
-         m_next_protocol = m_choose_next_protocol(pending_state.client_hello()->next_protocols());
-         }
       }
 
    if(resuming)
diff --git a/src/lib/tls/tls12/tls_server_impl_12.h b/src/lib/tls/tls12/tls_server_impl_12.h
index 0b73644e2..5f491eff9 100644
--- a/src/lib/tls/tls12/tls_server_impl_12.h
+++ b/src/lib/tls/tls12/tls_server_impl_12.h
@@ -12,7 +12,6 @@
 #include <botan/tls_policy.h>
 #include <botan/credentials_manager.h>
 #include <botan/internal/tls_channel_impl_12.h>
-#include <botan/internal/tls_server_impl.h>
 #include <vector>
 
 namespace Botan {
@@ -25,7 +24,7 @@ class Server_Handshake_State;
 /**
 * SSL/TLS Server 1.2 implementation
 */
-class Server_Impl_12 : public Channel_Impl_12, public Server_Impl
+class Server_Impl_12 : public Channel_Impl_12
    {
    public:
       typedef std::function<std::string(std::vector<std::string>)> next_protocol_fn;
@@ -59,14 +58,6 @@ class Server_Impl_12 : public Channel_Impl_12, public Server_Impl
                               bool is_datagram = false,
                               size_t reserved_io_buffer_size = TLS::Channel::IO_BUF_DEFAULT_SIZE);
    private:
-      /**
-      * Return the protocol notification set by the client (using the
-      * ALPN extension) for this connection, if any. This value is not
-      * tied to the session and a later renegotiation of the same
-      * session can choose a new protocol.
-      */
-      std::string next_protocol() const override { return m_next_protocol; }
-
       /**
       * Return the protocol notification set by the client (using the
       * ALPN extension) for this connection, if any. This value is not
@@ -119,9 +110,6 @@ class Server_Impl_12 : public Channel_Impl_12, public Server_Impl
 
       Credentials_Manager& m_creds;
       std::string m_next_protocol;
-
-      // Set by deprecated constructor, Server calls both this fn and Callbacks version
-      next_protocol_fn m_choose_next_protocol;
    };
 
 }
diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp
index f04df02b4..2667a2a9a 100644
--- a/src/lib/tls/tls13/tls_channel_impl_13.cpp
+++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp
@@ -122,20 +122,16 @@ size_t Channel_Impl_13::received_data(const uint8_t input[], size_t input_size)
                   // Note: Server_Hello_12 was deliberately not included in the check below because in TLS 1.2 Server Hello and
                   //       other handshake messages can be legally coalesced in a single record.
                   //
-                  if(holds_any_of<Client_Hello_13/*, EndOfEarlyData,*/, Server_Hello_13, Hello_Retry_Request, Finished_13>
+                  if(holds_any_of<Client_Hello_12, Client_Hello_13/*, EndOfEarlyData,*/, Server_Hello_13, Hello_Retry_Request, Finished_13>
                         (handshake_msg.value())
                         && m_handshake_layer.has_pending_data())
                      { throw Unexpected_Message("Unexpected additional handshake message data found in record"); }
 
-                  const bool downgrade_requested = std::holds_alternative<Server_Hello_12>(handshake_msg.value());
-
                   process_handshake_msg(std::move(handshake_msg.value()));
 
-                  if(downgrade_requested)
+                  if(is_downgrading())
                      {
                      // Downgrade to TLS 1.2 was detected. Stop everything we do and await being replaced by a 1.2 implementation.
-                     BOTAN_STATE_CHECK(m_downgrade_info);
-                     m_downgrade_info->will_downgrade = true;
                      return 0;
                      }
                   else if(m_downgrade_info != nullptr)
@@ -145,7 +141,7 @@ size_t Channel_Impl_13::received_data(const uint8_t input[], size_t input_size)
                      if(m_downgrade_info->received_tls_13_error_alert)
                         shutdown();
 
-                     // Downgrade can only happen if the first received message is a Server_Hello_12. This was not the case.
+                     // Downgrade can only be indicated in the first received peer message. This was not the case.
                      m_downgrade_info.reset();
                      }
 
@@ -370,29 +366,26 @@ void Channel_Impl_13::process_alert(const secure_vector<uint8_t>& record)
 
    // user canceled alerts are ignored
 
-   // TODO: the server doesn't have to expect downgrading; move this to the client
-   if(!expects_downgrade())
+   // RFC 8446 5.
+   //    All the alerts listed in Section 6.2 MUST be sent with
+   //    AlertLevel=fatal and MUST be treated as error alerts when received
+   //    regardless of the AlertLevel in the message.  Unknown Alert types
+   //    MUST be treated as error alerts.
+   if(is_error_alert(alert) && !alert.is_fatal())
       {
-      // RFC 8446 5.
-      //    All the alerts listed in Section 6.2 MUST be sent with
-      //    AlertLevel=fatal and MUST be treated as error alerts when received
-      //    regardless of the AlertLevel in the message.  Unknown Alert types
-      //    MUST be treated as error alerts.
-      if(is_error_alert(alert) && !alert.is_fatal())
+      // In TLS 1.2 error alerts might be marked as 'warnings' and would not
+      // demand an immediate shutdown. Until we are sure to talk to a TLS 1.3
+      // peer we must defer the shutdown and refrain from raising a decode
+      // error.
+      if(expects_downgrade())
+         {
+         m_downgrade_info->received_tls_13_error_alert = true;
+         }
+      else
          {
          throw TLS_Exception(Alert::DECODE_ERROR, "Error alert not marked fatal");  // will shutdown in send_alert
          }
       }
-   else
-      {
-      // Don't immediately shut down in case we might be dealing with a TLS 1.2 server. In this case,
-      // we cannot immediately shut down on alerts that are warnings in TLS 1.2.
-      // However, if the server turns out to _not_ downgrade, treat this as an error and do shut down.
-      // Note that this should not happen with a valid implementation, as the TLS 1.3 server shouldn't
-      // send a SERVER HELLO after the alert.
-      if(is_error_alert(alert))
-         m_downgrade_info->received_tls_13_error_alert = true;
-      }
 
    if(alert.is_fatal())
       shutdown();
@@ -421,6 +414,7 @@ void Channel_Impl_13::expect_downgrade(const Server_Information& server_info)
          {},
          {},
       server_info,
+      Botan::TLS::Channel::IO_BUF_DEFAULT_SIZE,
       callbacks(),
       session_manager(),
       credentials_manager(),
diff --git a/src/lib/tls/tls13/tls_channel_impl_13.h b/src/lib/tls/tls13/tls_channel_impl_13.h
index 7bb5f36c6..12eeb282e 100644
--- a/src/lib/tls/tls13/tls_channel_impl_13.h
+++ b/src/lib/tls/tls13/tls_channel_impl_13.h
@@ -117,7 +117,11 @@ class Channel_Impl_13 : public Channel_Impl
       * @return true iff the connection has been closed, i.e. CLOSE_NOTIFY
       * has been received from the peer.
       */
-      bool is_closed() const override { return !m_can_read; }
+      bool is_closed() const override { return is_closed_for_reading() && is_closed_for_writing(); }
+
+      bool is_closed_for_reading() const override { return !m_can_read; }
+      bool is_closed_for_writing() const override { return !m_can_write; }
+
 
       /**
       * Key material export (RFC 5705)
diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp
index 164a17e32..86541afbd 100644
--- a/src/lib/tls/tls13/tls_cipher_state.cpp
+++ b/src/lib/tls/tls13/tls_cipher_state.cpp
@@ -71,17 +71,19 @@
  *                                     |                     ClientHello...server Finished)
  *                                     |                     = exporter_master_secret
  *                                     *
- *                         STATE APPLICATION TRAFFIC
- * This state is reached by calling advance_with_server_finished(). The state can then be further
- * advanced using advance_with_client_finished().
+ *                      STATE SERVER APPLICATION TRAFFIC
+ * This state is reached by calling advance_with_server_finished(). It allows the server
+ * to send application traffic and the client to receive it. The opposite direction is not
+ * yet possible in this state. The state can then be further advanced using
+ * advance_with_client_finished().
  *                                     *
  *                                     |
  *                                     +-----> Derive-Secret(., "res master",
  *                                                           ClientHello...client Finished)
  *                                                           = resumption_master_secret
  *                             STATE COMPLETED
- * Once this state is reached the handshake is finished and no further cipher state advances
- * are possible.
+ * Once this state is reached the handshake is finished, both client and server can exchange
+ * application data and no further cipher state advances are possible.
  */
 
 #include <limits>
@@ -161,17 +163,16 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip
    {
    BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic);
 
-   zap(m_finished_key);
-   zap(m_peer_finished_key);
-
    const auto master_secret = hkdf_extract(secure_vector<uint8_t>(m_hash->output_length(), 0x00));
 
    auto client_application_traffic_secret = derive_secret(master_secret, "c ap traffic", transcript_hash);
    auto server_application_traffic_secret = derive_secret(master_secret, "s ap traffic", transcript_hash);
 
+   // Note: the secrets for processing client's application data
+   //       are not derived before the client's Finished message
+   //       was seen and the handshake can be considered finished.
    if(m_connection_side == Connection_Side::SERVER)
       {
-      derive_read_traffic_key(client_application_traffic_secret);
       derive_write_traffic_key(server_application_traffic_secret);
       m_read_application_traffic_secret = std::move(client_application_traffic_secret);
       m_write_application_traffic_secret      = std::move(server_application_traffic_secret);
@@ -179,19 +180,32 @@ void Cipher_State::advance_with_server_finished(const Transcript_Hash& transcrip
    else
       {
       derive_read_traffic_key(server_application_traffic_secret);
-      derive_write_traffic_key(client_application_traffic_secret);
       m_read_application_traffic_secret = std::move(server_application_traffic_secret);
       m_write_application_traffic_secret      = std::move(client_application_traffic_secret);
       }
 
    m_exporter_master_secret = derive_secret(master_secret, "exp master", transcript_hash);
 
-   m_state = State::ApplicationTraffic;
+   m_state = State::ServerApplicationTraffic;
    }
 
 void Cipher_State::advance_with_client_finished(const Transcript_Hash& transcript_hash)
    {
-   BOTAN_ASSERT_NOMSG(m_state == State::ApplicationTraffic);
+   BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic);
+
+   zap(m_finished_key);
+   zap(m_peer_finished_key);
+
+   // With the client's Finished message, the handshake is complete and
+   // we can process client application data.
+   if(m_connection_side == Connection_Side::SERVER)
+      {
+      derive_read_traffic_key(m_read_application_traffic_secret);
+      }
+   else
+      {
+      derive_write_traffic_key(m_write_application_traffic_secret);
+      }
 
    const auto master_secret = hkdf_extract(secure_vector<uint8_t>(m_hash->output_length(), 0x00));
 
@@ -265,6 +279,34 @@ size_t Cipher_State::minimum_decryption_input_length() const
    return m_decrypt->minimum_final_size();
    }
 
+bool Cipher_State::can_encrypt_application_traffic() const
+   {
+   // TODO: when implementing early traffic (0-RTT) this will likely need
+   //       to allow `State::EarlyTraffic`.
+
+   if(m_connection_side == Connection_Side::CLIENT && m_state != State::Completed)
+      { return false; }
+
+   if (m_connection_side == Connection_Side::SERVER && m_state != State::ServerApplicationTraffic && m_state != State::Completed)
+      { return false; }
+
+   return !m_write_key.empty() && !m_write_iv.empty();
+   }
+
+bool Cipher_State::can_decrypt_application_traffic() const
+   {
+   // TODO: when implementing early traffic (0-RTT) this will likely need
+   //       to allow `State::EarlyTraffic`.
+
+   if(m_connection_side == Connection_Side::CLIENT && m_state != State::ServerApplicationTraffic && m_state != State::Completed)
+      { return false; }
+
+   if(m_connection_side == Connection_Side::SERVER && m_state != State::Completed)
+      { return false; }
+
+   return !m_read_key.empty() && !m_read_iv.empty();
+   }
+
 bool Cipher_State::is_compatible_with(const Ciphersuite& cipher) const
    {
    if(!cipher.usable_in_version(Protocol_Version::TLS_V13))
@@ -297,7 +339,9 @@ std::vector<uint8_t> Cipher_State::psk_binder_mac(const Transcript_Hash& transcr
 
 std::vector<uint8_t> Cipher_State::finished_mac(const Transcript_Hash& transcript_hash) const
    {
-   BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic);
+   BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::SERVER || m_state == State::HandshakeTraffic);
+   BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::CLIENT || m_state == State::ServerApplicationTraffic);
+   BOTAN_ASSERT_NOMSG(!m_finished_key.empty());
 
    auto hmac = HMAC(m_hash->new_object());
    hmac.set_key(m_finished_key);
@@ -308,7 +352,9 @@ std::vector<uint8_t> Cipher_State::finished_mac(const Transcript_Hash& transcrip
 bool Cipher_State::verify_peer_finished_mac(const Transcript_Hash& transcript_hash,
       const std::vector<uint8_t>& peer_mac) const
    {
-   BOTAN_ASSERT_NOMSG(m_state == State::HandshakeTraffic);
+   BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::SERVER || m_state == State::ServerApplicationTraffic);
+   BOTAN_ASSERT_NOMSG(m_connection_side != Connection_Side::CLIENT || m_state == State::HandshakeTraffic);
+   BOTAN_ASSERT_NOMSG(!m_peer_finished_key.empty());
 
    auto hmac = HMAC(m_hash->new_object());
    hmac.set_key(m_peer_finished_key);
@@ -456,7 +502,7 @@ void Cipher_State::derive_read_traffic_key(const secure_vector<uint8_t>& traffic
    if(handshake_traffic_secret)
       {
       // Key derivation for the MAC in the "Finished" handshake message as described in RFC 8446 4.4.4
-      // (will be cleared in advance_with_server_finished())
+      // (will be cleared in advance_with_client_finished())
       m_peer_finished_key = hkdf_expand_label(traffic_secret, "finished", {}, m_hash->output_length());
       }
    }
@@ -519,7 +565,7 @@ std::vector<uint8_t> Cipher_State::empty_hash() const
 
 void Cipher_State::update_read_keys()
    {
-   BOTAN_ASSERT_NOMSG(m_state == State::ApplicationTraffic ||
+   BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic ||
                       m_state == State::Completed);
 
    m_read_application_traffic_secret =
@@ -530,7 +576,7 @@ void Cipher_State::update_read_keys()
 
 void Cipher_State::update_write_keys()
    {
-   BOTAN_ASSERT_NOMSG(m_state == State::ApplicationTraffic ||
+   BOTAN_ASSERT_NOMSG(m_state == State::ServerApplicationTraffic ||
                       m_state == State::Completed);
    m_write_application_traffic_secret =
       hkdf_expand_label(m_write_application_traffic_secret, "traffic upd", {}, m_hash->output_length());
diff --git a/src/lib/tls/tls13/tls_cipher_state.h b/src/lib/tls/tls13/tls_cipher_state.h
index 8706d86cc..58857467d 100644
--- a/src/lib/tls/tls13/tls_cipher_state.h
+++ b/src/lib/tls/tls13/tls_cipher_state.h
@@ -194,7 +194,7 @@ class BOTAN_TEST_API Cipher_State
       bool can_export_keys() const
          {
          return (m_state == State::EarlyTraffic       ||
-                 m_state == State::ApplicationTraffic ||
+                 m_state == State::ServerApplicationTraffic ||
                  m_state == State::Completed) &&
                 !m_exporter_master_secret.empty();
          }
@@ -202,13 +202,12 @@ class BOTAN_TEST_API Cipher_State
       /**
        * Indicates whether the appropriate secrets to encrypt application traffic are available
        */
-      bool can_encrypt_application_traffic() const
-         {
-         // TODO: when implementing early traffic (0-RTT) this will likely need
-         //       to allow `State::EarlyTraffic`.
-         return m_state != State::Uninitialized && m_state != State::HandshakeTraffic
-                && !m_write_key.empty() && !m_write_iv.empty();
-         }
+      bool can_encrypt_application_traffic() const;
+
+      /**
+       * Indicates whether the appropriate secrets to decrypt application traffic are available
+       */
+      bool can_decrypt_application_traffic() const;
 
       /**
        * @returns  true if the selected cipher primitives are compatible with
@@ -296,7 +295,7 @@ class BOTAN_TEST_API Cipher_State
          PskBinder,
          EarlyTraffic,
          HandshakeTraffic,
-         ApplicationTraffic,
+         ServerApplicationTraffic,
          Completed
          };
 
diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp
index aceb89c4a..3bd1c61ed 100644
--- a/src/lib/tls/tls13/tls_client_impl_13.cpp
+++ b/src/lib/tls/tls13/tls_client_impl_13.cpp
@@ -142,6 +142,15 @@ void Client_Impl_13::handle(const Server_Hello_12& server_hello_msg)
       throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Version downgrade received after Hello Retry");
       }
 
+   // RFC 8446 Appendix D.1
+   //    If the version chosen by the server is not supported by the client
+   //    (or is not acceptable), the client MUST abort the handshake with a
+   //    "protocol_version" alert.
+   if(!expects_downgrade())
+      {
+      throw TLS_Exception(Alert::PROTOCOL_VERSION, "Received an unexpected legacy Server Hello");
+      }
+
    // RFC 8446 4.1.3
    //    TLS 1.3 has a downgrade protection mechanism embedded in the server's
    //    random value.  TLS 1.3 servers which negotiate TLS 1.2 or below in
@@ -189,7 +198,7 @@ void Client_Impl_13::handle(const Server_Hello_12& server_hello_msg)
       throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Unexpected session ID during downgrade");
       }
 
-   BOTAN_ASSERT_NOMSG(expects_downgrade());
+   request_downgrade();
 
    // After this, no further messages are expected here because this instance will be replaced
    // by a Client_Impl_12.
@@ -562,8 +571,9 @@ void Client_Impl_13::handle(const Finished_13& finished_msg)
                            m_transcript_hash.previous()))
       { throw TLS_Exception(Alert::DECRYPT_ERROR, "Finished message didn't verify"); }
 
-   // save the current transcript hash as client auth might update the hash multiple times
-   const auto th_server_finished = m_transcript_hash.current();
+   // Derives the secrets for receiving application data but defers
+   // the derivation of sending application data.
+   m_cipher_state->advance_with_server_finished(m_transcript_hash.current());
 
    auto flight = aggregate_handshake_messages();
 
@@ -579,9 +589,7 @@ void Client_Impl_13::handle(const Finished_13& finished_msg)
 
    flight.send();
 
-   // derives the application traffic secrets and _replaces_ the handshake traffic secrets
-   // Note: this MUST happen AFTER the client finished message was sent!
-   m_cipher_state->advance_with_server_finished(th_server_finished);
+   // derives the sending application traffic secrets
    m_cipher_state->advance_with_client_finished(m_transcript_hash.current());
 
    // TODO: Create a dummy session object and invoke tls_session_established.
diff --git a/src/lib/tls/tls13/tls_handshake_layer_13.cpp b/src/lib/tls/tls13/tls_handshake_layer_13.cpp
index 63adb1ea5..7f7db1917 100644
--- a/src/lib/tls/tls13/tls_handshake_layer_13.cpp
+++ b/src/lib/tls/tls13/tls_handshake_layer_13.cpp
@@ -78,15 +78,15 @@ std::optional<Msg_Type> parse_message(TLS::TLS_Data_Reader& reader, const Policy
       {
       switch(type)
          {
+         // CLIENT_HELLO and SERVER_HELLO messages are ambiguous. Both may come
+         // from non-TLS 1.3 peers. Hence, their parsing is somewhat different.
          case CLIENT_HELLO:
-            return Client_Hello_13(msg);
+            // ... might be TLS 1.2 Client Hello or TLS 1.3 Client Hello
+            return generalize_to<Handshake_Message_13>(Client_Hello_13::parse(msg));
          case SERVER_HELLO:
-            // SERVER_HELLO might be either an actual server_hello (1.2 or 1.3) or a
-            // hello_retry_request. Hence, this construction is exceptionally
-            // funneled through a factory method and then transformed into a
-            // generic Handshake_Message_13.
-            return std::visit([](auto message) -> Handshake_Message_13
-               { return message; }, Server_Hello_13::parse(msg));
+            // ... might be TLS 1.2 Server Hello or TLS 1.3 Server Hello or
+            // a TLS 1.3 Hello Retry Request disguising as a Server Hello
+            return generalize_to<Handshake_Message_13>(Server_Hello_13::parse(msg));
          // case END_OF_EARLY_DATA:
          //    return End_Of_Early_Data(msg);
          case ENCRYPTED_EXTENSIONS:
diff --git a/src/lib/tls/tls13/tls_handshake_state_13.cpp b/src/lib/tls/tls13/tls_handshake_state_13.cpp
index 25b60b0e4..40114ec35 100644
--- a/src/lib/tls/tls13/tls_handshake_state_13.cpp
+++ b/src/lib/tls/tls13/tls_handshake_state_13.cpp
@@ -16,6 +16,12 @@ Client_Hello_13& Handshake_State_13_Base::store(Client_Hello_13 client_hello, co
     return m_client_hello.value();
 }
 
+Client_Hello_12& Handshake_State_13_Base::store(Client_Hello_12 client_hello, const bool)
+{
+    m_client_hello_12 = std::move(client_hello);
+    return m_client_hello_12.value();
+}
+
 Server_Hello_13& Handshake_State_13_Base::store(Server_Hello_13 server_hello, const bool)
 {
     m_server_hello = std::move(server_hello);
diff --git a/src/lib/tls/tls13/tls_handshake_state_13.h b/src/lib/tls/tls13/tls_handshake_state_13.h
index 284ac7aae..1ecbb8f39 100644
--- a/src/lib/tls/tls13/tls_handshake_state_13.h
+++ b/src/lib/tls/tls13/tls_handshake_state_13.h
@@ -50,6 +50,7 @@ class BOTAN_TEST_API Handshake_State_13_Base
       Handshake_State_13_Base(Connection_Side whoami) : m_side(whoami) {}
 
       Client_Hello_13&        store(Client_Hello_13 client_hello, const bool from_peer);
+      Client_Hello_12&        store(Client_Hello_12 client_hello, const bool from_peer);
       Server_Hello_13&        store(Server_Hello_13 server_hello, const bool from_peer);
       Server_Hello_12&        store(Server_Hello_12 server_hello, const bool from_peer);
       Hello_Retry_Request&    store(Hello_Retry_Request hello_retry_request, const bool from_peer);
@@ -79,6 +80,7 @@ class BOTAN_TEST_API Handshake_State_13_Base
       Connection_Side m_side;
 
       std::optional<Client_Hello_13> m_client_hello;
+      std::optional<Client_Hello_12> m_client_hello_12;
       std::optional<Server_Hello_13> m_server_hello;
       std::optional<Server_Hello_12> m_server_hello_12;
       std::optional<Hello_Retry_Request> m_hello_retry_request;
diff --git a/src/lib/tls/tls_channel.h b/src/lib/tls/tls_channel.h
index 5a8ed48fa..b34012875 100644
--- a/src/lib/tls/tls_channel.h
+++ b/src/lib/tls/tls_channel.h
@@ -102,10 +102,28 @@ class BOTAN_PUBLIC_API(2,0) Channel
       virtual bool is_active() const = 0;
 
       /**
+      * Note: For TLS 1.3 a connection is closed only after both peers have
+      *       signaled a "close_notify". While TLS 1.2 automatically responded
+      *       in suit once the peer had sent "close_notify", TLS 1.3 allows to
+      *       continue transmitting data even if the peer closed their writing
+      *       end.
+      *
       * @return true iff the connection has been definitely closed
       */
       virtual bool is_closed() const = 0;
 
+      /**
+      * @return true iff the peer closed their channel
+      *         (i.e. no more incoming data expected)
+      */
+      virtual bool is_closed_for_reading() const = 0;
+
+      /**
+      * @return true iff we closed our channel
+      *         (i.e. no more outgoing data allowed)
+      */
+      virtual bool is_closed_for_writing() const = 0;
+
       /**
       * @return certificate chain of the peer (may be empty)
       */
diff --git a/src/lib/tls/tls_channel_impl.h b/src/lib/tls/tls_channel_impl.h
index ab2feaf3b..c2e8500cf 100644
--- a/src/lib/tls/tls_channel_impl.h
+++ b/src/lib/tls/tls_channel_impl.h
@@ -26,6 +26,9 @@ class X509_Certificate;
 
 namespace TLS {
 
+class Client;
+class Server;
+
 class Channel_Impl
    {
    public:
@@ -76,6 +79,16 @@ class Channel_Impl
       */
       virtual bool is_closed() const = 0;
 
+      /**
+      * @return true iff the connection is active for sending application data
+      */
+      virtual bool is_closed_for_reading() const = 0;
+
+      /**
+      * @return true iff the connection has been definitely closed
+      */
+      virtual bool is_closed_for_writing() const = 0;
+
       /**
       * @return certificate chain of the peer (may be empty)
       */
@@ -121,6 +134,11 @@ class Channel_Impl
       */
       virtual bool timeout_check() = 0;
 
+      /**
+      * Return the protocol notification set for this connection, if any (ALPN).
+      * This value is not tied to the session and a later renegotiation of the
+      * same session can choose a new protocol.
+      */
       virtual std::string application_protocol() const = 0;
 
    protected:
@@ -143,6 +161,7 @@ class Channel_Impl
          std::vector<uint8_t> peer_transcript;
 
          Server_Information server_info;
+         size_t io_buffer_size;
 
          Callbacks& callbacks;
          Session_Manager& session_manager;
@@ -169,6 +188,28 @@ class Channel_Impl
          m_downgrade_info->client_hello_message = msg;
          }
 
+      friend class Client;
+      friend class Server;
+      void set_io_buffer_size(size_t io_buf_sz)
+         {
+         BOTAN_STATE_CHECK(m_downgrade_info);
+         m_downgrade_info->io_buffer_size = io_buf_sz;
+         }
+
+      /**
+       * Implementations use this to signal that the peer indicated a protocol
+       * version downgrade. After calling `request_downgrade()` no further
+       * state changes must be perfomed by the implementation. Particularly, no
+       * further handshake messages must be emitted. Instead, they must yield
+       * control flow back to the underlying Channel implementation to perform
+       * the protocol version downgrade.
+       */
+      void request_downgrade()
+         {
+         BOTAN_STATE_CHECK(m_downgrade_info && !m_downgrade_info->will_downgrade);
+         m_downgrade_info->will_downgrade = true;
+         }
+
    public:
       /**
        * Indicates whether a downgrade to TLS 1.2 or lower is in progress
diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp
index b4309a98a..2b8735ee8 100644
--- a/src/lib/tls/tls_client.cpp
+++ b/src/lib/tls/tls_client.cpp
@@ -48,9 +48,14 @@ Client::Client(Callbacks& callbacks,
       }
 
    if(effective_version == Protocol_Version::TLS_V13)
+      {
       m_impl = std::make_unique<Client_Impl_13>(
                   callbacks, session_manager, creds, policy,
                   rng, info, next_protocols);
+
+      if(m_impl->expects_downgrade())
+         { m_impl->set_io_buffer_size(io_buf_sz); }
+      }
    else
 #endif
       m_impl = std::make_unique<Client_Impl_12>(
@@ -87,6 +92,16 @@ bool Client::is_closed() const
    return m_impl->is_closed();
    }
 
+bool Client::is_closed_for_reading() const
+   {
+   return m_impl->is_closed_for_reading();
+   }
+
+bool Client::is_closed_for_writing() const
+   {
+   return m_impl->is_closed_for_writing();
+   }
+
 std::vector<X509_Certificate> Client::peer_cert_chain() const
    {
    return m_impl->peer_cert_chain();
diff --git a/src/lib/tls/tls_client.h b/src/lib/tls/tls_client.h
index 858f34eeb..fd07e928b 100644
--- a/src/lib/tls/tls_client.h
+++ b/src/lib/tls/tls_client.h
@@ -82,6 +82,9 @@ class BOTAN_PUBLIC_API(2,0) Client final : public Channel
 
       bool is_closed() const override;
 
+      bool is_closed_for_reading() const override;
+      bool is_closed_for_writing() const override;
+
       std::vector<X509_Certificate> peer_cert_chain() const override;
 
       SymmetricKey key_material_export(const std::string& label,
diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp
index 8896735c0..6b8493c60 100644
--- a/src/lib/tls/tls_extensions.cpp
+++ b/src/lib/tls/tls_extensions.cpp
@@ -233,7 +233,7 @@ Unknown_Extension::Unknown_Extension(Handshake_Extension_Type type,
 
 std::vector<uint8_t> Unknown_Extension::serialize(Connection_Side /*whoami*/) const
    {
-   throw Invalid_State("Cannot encode an unknown TLS extension");
+   return m_value;
    }
 
 Server_Name_Indicator::Server_Name_Indicator(TLS_Data_Reader& reader,
@@ -268,8 +268,17 @@ Server_Name_Indicator::Server_Name_Indicator(TLS_Data_Reader& reader,
       }
    }
 
-std::vector<uint8_t> Server_Name_Indicator::serialize(Connection_Side /*whoami*/) const
+std::vector<uint8_t> Server_Name_Indicator::serialize(Connection_Side whoami) const
    {
+   // RFC 6066
+   //    [...] the server SHALL include an extension of type "server_name" in
+   //    the (extended) server hello. The "extension_data" field of this
+   //    extension SHALL be empty.
+   if(whoami == Connection_Side::SERVER)
+      {
+      return {};
+      }
+
    std::vector<uint8_t> buf;
 
    size_t name_len = m_sni_host_name.size();
@@ -484,18 +493,20 @@ Supported_Point_Formats::Supported_Point_Formats(TLS_Data_Reader& reader,
       }
    }
 
-std::vector<uint8_t> Signature_Algorithms::serialize(Connection_Side /*whoami*/) const
+namespace {
+
+std::vector<uint8_t> serialize_signature_algorithms(const std::vector<Signature_Scheme>& schemes)
    {
-   BOTAN_ASSERT(m_schemes.size() < 256, "Too many signature schemes");
+   BOTAN_ASSERT(schemes.size() < 256, "Too many signature schemes");
 
    std::vector<uint8_t> buf;
 
-   const uint16_t len = static_cast<uint16_t>(m_schemes.size() * 2);
+   const uint16_t len = static_cast<uint16_t>(schemes.size() * 2);
 
    buf.push_back(get_byte<0>(len));
    buf.push_back(get_byte<1>(len));
 
-   for(Signature_Scheme scheme : m_schemes)
+   for(Signature_Scheme scheme : schemes)
       {
       buf.push_back(get_byte<0>(scheme.wire_code()));
       buf.push_back(get_byte<1>(scheme.wire_code()));
@@ -504,8 +515,8 @@ std::vector<uint8_t> Signature_Algorithms::serialize(Connection_Side /*whoami*/)
    return buf;
    }
 
-Signature_Algorithms::Signature_Algorithms(TLS_Data_Reader& reader,
-                                           uint16_t extension_size)
+std::vector<Signature_Scheme> parse_signature_algorithms(TLS_Data_Reader& reader,
+                                                         uint16_t extension_size)
    {
    uint16_t len = reader.get_uint16_t();
 
@@ -514,13 +525,43 @@ Signature_Algorithms::Signature_Algorithms(TLS_Data_Reader& reader,
       throw Decoding_Error("Bad encoding on signature algorithms extension");
       }
 
+   std::vector<Signature_Scheme> schemes;
+   schemes.reserve(len / 2);
    while(len)
       {
-      m_schemes.emplace_back(reader.get_uint16_t());
+      schemes.emplace_back(reader.get_uint16_t());
       len -= 2;
       }
+
+   return schemes;
+   }
+
+}
+
+std::vector<uint8_t> Signature_Algorithms::serialize(Connection_Side /*whoami*/) const
+   {
+   return serialize_signature_algorithms(m_schemes);
+   }
+
+Signature_Algorithms::Signature_Algorithms(TLS_Data_Reader& reader,
+                                           uint16_t extension_size)
+   : m_schemes(parse_signature_algorithms(reader, extension_size))
+   {}
+
+#if defined(BOTAN_HAS_TLS_13)
+
+std::vector<uint8_t> Signature_Algorithms_Cert::serialize(Connection_Side /*whoami*/) const
+   {
+   return serialize_signature_algorithms(m_schemes);
    }
 
+Signature_Algorithms_Cert::Signature_Algorithms_Cert(TLS_Data_Reader& reader,
+                                                     uint16_t extension_size)
+   : m_schemes(parse_signature_algorithms(reader, extension_size))
+   {}
+
+#endif
+
 Session_Ticket::Session_Ticket(TLS_Data_Reader& reader,
                                uint16_t extension_size) : m_ticket(reader.get_elem<uint8_t, std::vector<uint8_t>>(extension_size))
    {}
@@ -834,21 +875,6 @@ Certificate_Authorities::Certificate_Authorities(TLS_Data_Reader& reader, uint16
    }
 
 
-Signature_Algorithms_Cert::Signature_Algorithms_Cert(const std::vector<Signature_Scheme>& schemes)
-      : m_siganture_algorithms(schemes)
-   {
-   }
-
-Signature_Algorithms_Cert::Signature_Algorithms_Cert(TLS_Data_Reader& reader, uint16_t extension_size)
-   : m_siganture_algorithms(reader, extension_size)
-   {
-   }
-
-std::vector<uint8_t> Signature_Algorithms_Cert::serialize(Connection_Side whoami) const
-   {
-      return m_siganture_algorithms.serialize(whoami);
-   }
-
 std::vector<uint8_t> EarlyDataIndication::serialize(Connection_Side) const
    {
    std::vector<uint8_t> result;
diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h
index b21e383ad..f9757d5ce 100644
--- a/src/lib/tls/tls_extensions.h
+++ b/src/lib/tls/tls_extensions.h
@@ -136,7 +136,8 @@ class BOTAN_UNSTABLE_API Server_Name_Indicator final : public Extension
 
       std::vector<uint8_t> serialize(Connection_Side whoami) const override;
 
-      bool empty() const override { return m_sni_host_name.empty(); }
+      bool empty() const override { return false; }
+
    private:
       std::string m_sni_host_name;
    };
@@ -327,8 +328,8 @@ class BOTAN_UNSTABLE_API Signature_Algorithms final : public Extension
 
       bool empty() const override { return m_schemes.empty(); }
 
-      explicit Signature_Algorithms(const std::vector<Signature_Scheme>& schemes) :
-         m_schemes(schemes) {}
+      explicit Signature_Algorithms(std::vector<Signature_Scheme> schemes) :
+         m_schemes(std::move(schemes)) {}
 
       Signature_Algorithms(TLS_Data_Reader& reader,
                            uint16_t extension_size);
@@ -336,6 +337,43 @@ class BOTAN_UNSTABLE_API Signature_Algorithms final : public Extension
       std::vector<Signature_Scheme> m_schemes;
    };
 
+#if defined(BOTAN_HAS_TLS_13)
+
+/**
+* Signature_Algorithms_Cert for TLS 1.3 (RFC 8446)
+*
+* RFC 8446 4.2.3
+*    TLS 1.3 provides two extensions for indicating which signature algorithms
+*    may be used in digital signatures.  The "signature_algorithms_cert"
+*    extension applies to signatures in certificates, and the
+*    "signature_algorithms" extension, which originally appeared in TLS 1.2,
+*    applies to signatures in CertificateVerify messages.
+*/
+class BOTAN_UNSTABLE_API Signature_Algorithms_Cert final : public Extension
+   {
+   public:
+      static Handshake_Extension_Type static_type()
+         { return TLSEXT_SIGNATURE_ALGORITHMS_CERT; }
+
+      Handshake_Extension_Type type() const override { return static_type(); }
+
+      const std::vector<Signature_Scheme>& supported_schemes() const { return m_schemes; }
+
+      std::vector<uint8_t> serialize(Connection_Side whoami) const override;
+
+      bool empty() const override { return m_schemes.empty(); }
+
+      explicit Signature_Algorithms_Cert(std::vector<Signature_Scheme> schemes)
+         : m_schemes(std::move(schemes)) {}
+
+      Signature_Algorithms_Cert(TLS_Data_Reader& reader, uint16_t extension_size);
+
+   private:
+      std::vector<Signature_Scheme> m_schemes;
+   };
+
+#endif
+
 /**
 * Used to indicate SRTP algorithms for DTLS (RFC 5764)
 */
@@ -636,31 +674,6 @@ class BOTAN_UNSTABLE_API PSK final : public Extension
       std::unique_ptr<PSK_Internal> m_impl;
    };
 
-/**
-* Signature_Algorithms_Cert from RFC 8446
-*/
-class BOTAN_UNSTABLE_API Signature_Algorithms_Cert final : public Extension
-   {
-   public:
-      static Handshake_Extension_Type static_type()
-         { return TLSEXT_SIGNATURE_ALGORITHMS_CERT; }
-
-      Handshake_Extension_Type type() const override { return static_type(); }
-
-      const std::vector<Signature_Scheme>& supported_schemes() const { return m_siganture_algorithms.supported_schemes(); }
-
-      std::vector<uint8_t> serialize(Connection_Side whoami) const override;
-
-      bool empty() const override { return m_siganture_algorithms.empty(); }
-
-      explicit Signature_Algorithms_Cert(const std::vector<Signature_Scheme>& schemes);
-
-      Signature_Algorithms_Cert(TLS_Data_Reader& reader, uint16_t extension_size);
-
-   private:
-      const Signature_Algorithms m_siganture_algorithms;
-   };
-
 /**
 * Key_Share from RFC 8446 4.2.8
 */
@@ -754,7 +767,7 @@ class BOTAN_UNSTABLE_API Unknown_Extension final : public Extension
                         TLS_Data_Reader& reader,
                         uint16_t extension_size);
 
-      std::vector<uint8_t> serialize(Connection_Side whoami) const override; // always fails
+      std::vector<uint8_t> serialize(Connection_Side whoami) const override;
 
       const std::vector<uint8_t>& value() { return m_value; }
 
diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h
index 117def351..83efa1f75 100644
--- a/src/lib/tls/tls_messages.h
+++ b/src/lib/tls/tls_messages.h
@@ -67,12 +67,21 @@ class BOTAN_UNSTABLE_API Hello_Verify_Request final : public Handshake_Message
       std::vector<uint8_t> m_cookie;
    };
 
+class Client_Hello_Internal;
+
 /**
 * Client Hello Message
 */
 class BOTAN_UNSTABLE_API Client_Hello : public Handshake_Message
    {
    public:
+      Client_Hello(const Client_Hello&) = delete;
+      Client_Hello& operator=(const Client_Hello&) = delete;
+      Client_Hello(Client_Hello&&);
+      Client_Hello& operator=(Client_Hello&&);
+
+      ~Client_Hello();
+
       Handshake_Type type() const override;
 
       /**
@@ -125,22 +134,13 @@ class BOTAN_UNSTABLE_API Client_Hello : public Handshake_Message
       const Extensions& extensions() const;
 
    protected:
-      Client_Hello() : m_comp_methods({ 0 }) {}
-
-      explicit Client_Hello(const std::vector<uint8_t>& buf);
+      Client_Hello();
+      explicit Client_Hello(std::unique_ptr<Client_Hello_Internal> data);
 
       const std::vector<uint8_t>& compression_methods() const;
 
    protected:
-      Protocol_Version m_legacy_version;
-      std::vector<uint8_t> m_session_id;
-      std::vector<uint8_t> m_random;
-      std::vector<uint16_t> m_suites;
-      std::vector<uint8_t> m_comp_methods;
-      Extensions m_extensions;
-
-      std::vector<uint8_t> m_hello_cookie; // DTLS only
-      std::vector<uint8_t> m_cookie_input_bits; // DTLS only
+      std::unique_ptr<Client_Hello_Internal> m_data;
    };
 
 class BOTAN_UNSTABLE_API Client_Hello_12 final : public Client_Hello
@@ -163,7 +163,7 @@ class BOTAN_UNSTABLE_API Client_Hello_12 final : public Client_Hello
          };
 
    public:
-      explicit Client_Hello_12(const std::vector<uint8_t>& buf) : Client_Hello(buf) {}
+      explicit Client_Hello_12(const std::vector<uint8_t>& buf);
 
       Client_Hello_12(Handshake_IO& io,
                       Handshake_Hash& hash,
@@ -183,6 +183,11 @@ class BOTAN_UNSTABLE_API Client_Hello_12 final : public Client_Hello
                       const Session& session,
                       const std::vector<std::string>& next_protocols);
 
+   protected:
+      friend class Client_Hello_13;  // to allow construction by Client_Hello_13::parse()
+      Client_Hello_12(std::unique_ptr<Client_Hello_Internal> data);
+
+   public:
       using Client_Hello::random;
       using Client_Hello::compression_methods;
 
@@ -210,8 +215,6 @@ class BOTAN_UNSTABLE_API Client_Hello_12 final : public Client_Hello
 class BOTAN_UNSTABLE_API Client_Hello_13 final : public Client_Hello
    {
    public:
-      explicit Client_Hello_13(const std::vector<uint8_t>& buf) : Client_Hello(buf) {}
-
       Client_Hello_13(const Policy& policy,
                       Callbacks& cb,
                       RandomNumberGenerator& rng,
@@ -219,14 +222,17 @@ class BOTAN_UNSTABLE_API Client_Hello_13 final : public Client_Hello
                       const std::vector<std::string>& next_protocols,
                       const std::optional<Session>& session = std::nullopt);
 
+      static std::variant<Client_Hello_13, Client_Hello_12>
+      parse(const std::vector<uint8_t>& buf);
+
       void retry(const Hello_Retry_Request& hrr,
                  const Transcript_Hash_State& transcript_hash_state,
                  Callbacks& cb,
                  RandomNumberGenerator& rng);
 
-      std::vector<Protocol_Version> supported_versions() const;
-
    private:
+      Client_Hello_13(std::unique_ptr<Client_Hello_Internal> data);
+
       /**
        * If the Client Hello contains a PSK extensions with identities this will
        * generate the PSK binders as described in RFC 8446 4.2.11.2.
@@ -949,6 +955,7 @@ using as_wrapped_references_t = typename as_wrapped_references<T>::type;
 // Handshake message types from RFC 8446 4.
 using Handshake_Message_13 = std::variant<
                              Client_Hello_13,
+                             Client_Hello_12,
                              Server_Hello_13,
                              Server_Hello_12,
                              Hello_Retry_Request,
@@ -977,6 +984,7 @@ using Server_Handshake_13_Message_Ref = as_wrapped_references_t<Server_Handshake
 
 using Client_Handshake_13_Message = std::variant<
                                     Client_Hello_13,
+                                    Client_Hello_12,  // indicates a TLS peer that does not offer TLS 1.3
                                     Certificate_13,
                                     Certificate_Verify_13,
                                     Finished_13>;
diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp
index 8b8b3237a..67a0e8c82 100644
--- a/src/lib/tls/tls_server.cpp
+++ b/src/lib/tls/tls_server.cpp
@@ -13,7 +13,6 @@
 #include <botan/internal/tls_handshake_state.h>
 #include <botan/internal/stl_util.h>
 #include <botan/internal/tls_server_impl_12.h>
-#include <botan/internal/tls_server_impl.h>
 #include <botan/tls_magic.h>
 
 namespace Botan::TLS {
@@ -37,79 +36,84 @@ Server::~Server() = default;
 
 size_t Server::received_data(const uint8_t buf[], size_t buf_size)
    {
-   return m_impl->channel().received_data(buf, buf_size);
+   return m_impl->received_data(buf, buf_size);
    }
 
 bool Server::is_active() const
    {
-   return m_impl->channel().is_active();
+   return m_impl->is_active();
    }
 
 bool Server::is_closed() const
    {
-   return m_impl->channel().is_closed();
+   return m_impl->is_closed();
+   }
+
+bool Server::is_closed_for_reading() const
+   {
+   return m_impl->is_closed_for_reading();
+   }
+
+bool Server::is_closed_for_writing() const
+   {
+   return m_impl->is_closed_for_writing();
    }
 
 std::vector<X509_Certificate> Server::peer_cert_chain() const
    {
-   return m_impl->channel().peer_cert_chain();
+   return m_impl->peer_cert_chain();
    }
 
 SymmetricKey Server::key_material_export(const std::string& label,
       const std::string& context,
       size_t length) const
    {
-   return m_impl->channel().key_material_export(label, context, length);
+   return m_impl->key_material_export(label, context, length);
    }
 
 void Server::renegotiate(bool force_full_renegotiation)
    {
-   m_impl->channel().renegotiate(force_full_renegotiation);
+   m_impl->renegotiate(force_full_renegotiation);
    }
 
 void Server::update_traffic_keys(bool request_peer_update)
    {
-   m_impl->channel().update_traffic_keys(request_peer_update);
+   m_impl->update_traffic_keys(request_peer_update);
    }
 
 bool Server::secure_renegotiation_supported() const
    {
-   return m_impl->channel().secure_renegotiation_supported();
+   return m_impl->secure_renegotiation_supported();
    }
 
 void Server::send(const uint8_t buf[], size_t buf_size)
    {
-   m_impl->channel().send(buf, buf_size);
+   m_impl->send(buf, buf_size);
    }
 
 void Server::send_alert(const Alert& alert)
    {
-   m_impl->channel().send_alert(alert);
+   m_impl->send_alert(alert);
    }
 
 void Server::send_warning_alert(Alert::Type type)
    {
-   m_impl->channel().send_warning_alert(type);
+   m_impl->send_warning_alert(type);
    }
 
 void Server::send_fatal_alert(Alert::Type type)
    {
-   m_impl->channel().send_fatal_alert(type);
+   m_impl->send_fatal_alert(type);
    }
 
 void Server::close()
    {
-   m_impl->channel().close();
+   m_impl->close();
    }
 
 bool Server::timeout_check()
    {
-   return m_impl->channel().timeout_check();
-   }
-
-std::string Server::next_protocol() const
-   {
-   return m_impl->next_protocol();
+   return m_impl->timeout_check();
    }
 
 std::string Server::application_protocol() const
diff --git a/src/lib/tls/tls_server.h b/src/lib/tls/tls_server.h
index 067d5b83c..1a3e4fe98 100644
--- a/src/lib/tls/tls_server.h
+++ b/src/lib/tls/tls_server.h
@@ -20,7 +20,7 @@ namespace Botan {
 
 namespace TLS {
 
-class Server_Impl;
+class Channel_Impl;
 
 /**
 * TLS Server
@@ -60,14 +60,6 @@ class BOTAN_PUBLIC_API(2,0) Server final : public Channel
 
       ~Server();
 
-      /**
-      * Return the protocol notification set by the client (using the
-      * ALPN extension) for this connection, if any. This value is not
-      * tied to the session and a later renegotiation of the same
-      * session can choose a new protocol.
-      */
-      std::string next_protocol() const;
-
       /**
       * Return the protocol notification set by the client (using the
       * ALPN extension) for this connection, if any. This value is not
@@ -84,6 +76,9 @@ class BOTAN_PUBLIC_API(2,0) Server final : public Channel
 
       bool is_closed() const override;
 
+      bool is_closed_for_reading() const override;
+      bool is_closed_for_writing() const override;
+
       std::vector<X509_Certificate> peer_cert_chain() const override;
 
       SymmetricKey key_material_export(const std::string& label,
@@ -111,7 +106,7 @@ class BOTAN_PUBLIC_API(2,0) Server final : public Channel
       bool timeout_check() override;
 
    private:
-      std::unique_ptr<Server_Impl> m_impl;
+      std::unique_ptr<Channel_Impl> m_impl;
    };
 }
 
diff --git a/src/lib/tls/tls_server_impl.h b/src/lib/tls/tls_server_impl.h
deleted file mode 100644
index 4c4d6f0f8..000000000
--- a/src/lib/tls/tls_server_impl.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-* TLS Server
-* (C) 2004-2011 Jack Lloyd
-*     2016 Matthias Gierlings
-*     2021 Elektrobit Automotive GmbH
-*
-* Botan is released under the Simplified BSD License (see license.txt)
-*/
-
-#ifndef BOTAN_TLS_SERVER_IMPL_H_
-#define BOTAN_TLS_SERVER_IMPL_H_
-
-#include <botan/tls_magic.h>
-#include <vector>
-#include <memory>
-#include <string>
-
-namespace Botan {
-
-namespace TLS {
-
-class Handshake_State;
-class Handshake_IO;
-class Channel_Impl;
-
-/**
-* Interface of pimpl for Server
-*/
-class Server_Impl
-   {
-   public:
-      virtual ~Server_Impl() = default;
-
-      explicit Server_Impl(Channel_Impl& impl) : m_impl{impl} {}
-
-      Channel_Impl& channel() { return m_impl; }
-
-      /**
-      * Return the protocol notification set by the client (using the
-      * ALPN extension) for this connection, if any. This value is not
-      * tied to the session and a later renegotiation of the same
-      * session can choose a new protocol.
-      */
-      virtual std::string next_protocol() const = 0;
-
-      /**
-      * Return the protocol notification set by the client (using the
-      * ALPN extension) for this connection, if any. This value is not
-      * tied to the session and a later renegotiation of the same
-      * session can choose a new protocol.
-      */
-      virtual std::string application_protocol() const = 0;
-
-      virtual void initiate_handshake(Handshake_State& state,
-                                      bool force_full_renegotiation) = 0;
-
-      virtual void process_handshake_msg(const Handshake_State* active_state,
-                                         Handshake_State& pending_state,
-                                         Handshake_Type type,
-                                         const std::vector<uint8_t>& contents,
-                                         bool epoch0_restart) = 0;
-
-      virtual std::unique_ptr<Handshake_State> new_handshake_state(std::unique_ptr<Handshake_IO> io) = 0;
-
-   private:
-      Channel_Impl& m_impl;
-   };
-}
-}
-
-#endif
diff --git a/src/lib/utils/stl_util.h b/src/lib/utils/stl_util.h
index e46b2f196..9d89c2988 100644
--- a/src/lib/utils/stl_util.h
+++ b/src/lib/utils/stl_util.h
@@ -141,6 +141,48 @@ constexpr bool holds_any_of(const std::variant<Ts...>& v) noexcept {
     return (std::holds_alternative<Alts>(v) || ...);
 }
 
+template<typename GeneralVariantT, typename SpecialT>
+constexpr bool is_generalizable_to(const SpecialT&) noexcept
+   {
+   return std::is_constructible_v<GeneralVariantT, SpecialT>;
+   }
+
+template<typename GeneralVariantT, typename... SpecialTs>
+constexpr bool is_generalizable_to(const std::variant<SpecialTs...>&) noexcept
+   {
+   return (std::is_constructible_v<GeneralVariantT, SpecialTs> && ...);
+   }
+
+/**
+ * @brief Converts a given variant into another variant-ish whose type states
+ *        are a super set of the given variant.
+ *
+ * This is useful to convert restricted variant types into more general
+ * variants types.
+ */
+template<typename GeneralVariantT, typename SpecialT>
+constexpr GeneralVariantT generalize_to(SpecialT&& specific) noexcept
+   {
+   static_assert(std::is_constructible_v<GeneralVariantT, std::decay_t<SpecialT>>,
+                 "Desired general type must be implicitly constructible by the specific type");
+   return std::forward<SpecialT>(specific);
+   }
+
+/**
+ * @brief Converts a given variant into another variant-ish whose type states
+ *        are a super set of the given variant.
+ *
+ * This is useful to convert restricted variant types into more general
+ * variants types.
+ */
+template<typename GeneralVariantT, typename... SpecialTs>
+constexpr GeneralVariantT generalize_to(std::variant<SpecialTs...> specific) noexcept
+   {
+   static_assert(is_generalizable_to<GeneralVariantT>(specific),
+                 "Desired general type must be implicitly constructible by all types of the specialized std::variant<>");
+   return std::visit([](auto s) -> GeneralVariantT { return s; }, std::move(specific));
+   }
+
 // This is a helper utility to emulate pattern matching with std::visit.
 // See https://en.cppreference.com/w/cpp/utility/variant/visit for more info.
 template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };


Test patch:
diff --git a/src/tests/data/tls_13/client_hello.vec b/src/tests/data/tls_13/client_hello.vec
new file mode 100644
index 000000000..0960b0180
--- /dev/null
+++ b/src/tests/data/tls_13/client_hello.vec
@@ -0,0 +1,116 @@
+# Tests generated partially with openssl 1.0.2g/1.1.0a, TLS-Attacker and taken from RFC 8448 (for TLS 1.3)
+# ClientHello message contains many fields, the following fields are checked:
+# - Protocol Version
+# - Message Type
+# - Extensions
+
+[client_hello]
+# no extension (empty renegotiation generated)
+Buffer = 030320f3dc33f90be6509e6133a1819f2b80fe6ccc6268d9195ca4ead7504ffe7e2a0000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000000
+Protocol = 0303
+AdditionalData = FF01
+Ciphersuite = C030C02CC028C024C014C00A00A500A300A1009F006B006A0069006800390038003700360088008700860085C032C02EC02AC026C00FC005009D003D00350084C02FC02BC027C023C013C00900A400A200A0009E00670040003F003E0033003200310030009A0099009800970045004400430042C031C02DC029C025C00EC004009C003C002F00960041C011C007C00CC00200050004C012C008001600130010000DC00DC003000A00FF
+Message_Type = client_hello_12
+Exception =
+
+# with extensions: point formats, ec curves, session ticket, signature algorithms, heartbeat (point formats and heartbeat not supported, empty renegotiation generated)
+Buffer = 0303871e18983024eaee1be8ae6607d5ecad941d33fd7fc1d8554a9e1fbfda8d30880000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101
+Protocol = 0303
+AdditionalData = 000A000B000D000F0023FF01
+Ciphersuite = C030C02CC028C024C014C00A00A500A300A1009F006B006A0069006800390038003700360088008700860085C032C02EC02AC026C00FC005009D003D00350084C02FC02BC027C023C013C00900A400A200A0009E00670040003F003E0033003200310030009A0099009800970045004400430042C031C02DC029C025C00EC004009C003C002F00960041C011C007C00CC00200050004C012C008001600130010000DC00DC003000A00FF
+Message_Type = client_hello_12
+Exception =
+
+# with extensions: point formats, ec curves, session ticket, signature algorithms, heartbeat, Encrypt-then-MAC, Extended Master Secret (point formats and heartbeat not supported, empty renegotiation generated)
+Buffer = 0303e00da23523058b5dc9c445d97b2bb6315b019e97838ac4f16c23b2cb031b6a490000e2c0afc0adc030c02cc028c024c014c00ac0a3c09f00a500a300a1009f006b006a006900680039003800370036cca9cca8c077c073ccaa00c400c300c200c10088008700860085c032c02ec02ac026c00fc005c079c075c0a1c09d009d003d003500c00084c0aec0acc02fc02bc027c023c013c009c0a2c09e00a400a200a0009e00670040003f003e0033003200310030c076c07200be00bd00bc00bb009a0099009800970045004400430042c031c02dc029c025c00ec004c078c074c0a0c09c009c003c002f00ba009600410007c012c008001600130010000dc00dc003000a00ff0100005f000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d00220020060106020603050105020503040104020403030103020303020102020203eded000f0001010016000000170000
+Protocol = 0303
+AdditionalData = 000A000B000D000F001600170023FF01
+Ciphersuite = C0AFC0ADC030C02CC028C024C014C00AC0A3C09F00A500A300A1009F006B006A006900680039003800370036CCA9CCA8C077C073CCAA00C400C300C200C10088008700860085C032C02EC02AC026C00FC005C079C075C0A1C09D009D003D003500C00084C0AEC0ACC02FC02BC027C023C013C009C0A2C09E00A400A200A0009E00670040003F003E0033003200310030C076C07200BE00BD00BC00BB009A0099009800970045004400430042C031C02DC029C025C00EC004C078C074C0A0C09C009C003C002F00BA009600410007C012C008001600130010000DC00DC003000A00FF
+Message_Type = client_hello_12
+Exception =
+
+# basic TLS 1.3 client hello from RFC 8448
+Buffer = 0303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Protocol = 0304
+AdditionalData = 0000000A000D001C0023002B002D0033FF01
+Ciphersuite = 130113031302
+Message_Type = client_hello_13
+Exception =
+
+# TLS 1.3 client hello with legacy_version != 0303
+Buffer = 0304cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Protocol = 0304
+AdditionalData = 0000000A000D001C0023002B002D0033FF01
+Ciphersuite = 130113031302
+Message_Type = client_hello_13
+Exception = TLS 1.3 Client Hello has invalid legacy_version
+
+# TLS 1.3 client hello with single compression method '01'
+Buffer = 0303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010100910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Protocol = 0304
+AdditionalData = 0000000A000D001C0023002B002D0033FF01
+Ciphersuite = 130113031302
+Message_Type = client_hello_13
+Exception = Client sent TLS 1.3 Client Hello with compression method != null
+
+# TLS 1.3 client hello with two compression methods '00' and '01'
+Buffer = 0303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece700000613011303130202000100910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Protocol = 0304
+AdditionalData = 0000000A000D001C0023002B002D0033FF01
+Ciphersuite = 130113031302
+Message_Type = client_hello_13
+Exception = Client sent TLS 1.3 Client Hello with compression method != null
+
+# empty
+Buffer =
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Client_Hello: Packet corrupted
+
+Buffer = 00
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Client_Hello: Packet corrupted
+
+# Invalid cipher suite length (0xf0e2 instead of 0x00e2)
+Buffer = 0303e00da23523058b5dc9c445d97b2bb6315b019e97838ac4f16c23b2cb031b6a4900f0e2c0afc0adc030c02cc028c024c014c00ac0a3c09f00a500a300a1009f006b006a006900680039003800370036cca9cca8c077c073ccaa00c400c300c200c10088008700860085c032c02ec02ac026c00fc005c079c075c0a1c09d009d003d003500c00084c0aec0acc02fc02bc027c023c013c009c0a2c09e00a400a200a0009e00670040003f003e0033003200310030c076c07200be00bd00bc00bb009a0099009800970045004400430042c031c02dc029c025c00ec004c078c074c0a0c09c009c003c002f00ba009600410007c012c008001600130010000dc00dc003000a00ff01000000
+Protocol = 0303
+AdditionalData =
+Message_Type = client_hello_12
+Exception = Invalid ClientHello: Expected 61666 bytes remaining, only 230 left
+
+#invalid extensions length
+Buffer = 030320f3dc33f90be6509e6133a1819f2b80fe6ccc6268d9195ca4ead7504ffe7e2a0000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000001
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Bad extension size
+
+#invalid extensions length 2
+Buffer = 030320f3dc33f90be6509e6133a1819f2b80fe6ccc6268d9195ca4ead7504ffe7e2a0000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff010000010000
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Bad extension size
+
+#invalid length of the supported groups  extension (0xf01c instead of 0x001c)
+Buffer = 0303871e18983024eaee1be8ae6607d5ecad941d33fd7fc1d8554a9e1fbfda8d30880000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000055000b000403000102000af01c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Inconsistent length field in supported groups list
+
+#invalid length of the supported groups  extension (0xf01a instead of 0x001a)
+Buffer = 0303871e18983024eaee1be8ae6607d5ecad941d33fd7fc1d8554a9e1fbfda8d30880000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000055000b000403000102000a001cf01a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Inconsistent length field in supported groups list
+
+#invalid length of the session ticket extension
+Buffer = 0303871e18983024eaee1be8ae6607d5ecad941d33fd7fc1d8554a9e1fbfda8d30880000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a002300ff000d0020001e060106020603050105020503040104020403030103020303020102020203000f000101
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Invalid ClientHello: Expected 255 bytes remaining, only 41 left
+
+#invalid length of the heartbeat extension
+Buffer = 0303871e18983024eaee1be8ae6607d5ecad941d33fd7fc1d8554a9e1fbfda8d30880000aac030c02cc028c024c014c00a00a500a300a1009f006b006a0069006800390038003700360088008700860085c032c02ec02ac026c00fc005009d003d00350084c02fc02bc027c023c013c00900a400a200a0009e00670040003f003e0033003200310030009a0099009800970045004400430042c031c02dc029c025c00ec004009c003c002f00960041c011c007c00cc00200050004c012c008001600130010000dc00dc003000a00ff01000055000b000403000102000a001c001a00170019001c001b0018001a0016000e000d000b000c0009000a00230000000d0020001e060106020603050105020503040104020403030103020303020102020203000f000201
+Protocol = 0303
+Message_Type = client_hello_12
+Exception = Invalid ClientHello: Expected 2 bytes remaining, only 1 left
diff --git a/src/tests/data/tls_13_rfc8448/transcripts.vec b/src/tests/data/tls_13_rfc8448/transcripts.vec
index f303b00b8..0d339b2be 100644
--- a/src/tests/data/tls_13_rfc8448/transcripts.vec
+++ b/src/tests/data/tls_13_rfc8448/transcripts.vec
@@ -1,69 +1,96 @@
 [Simple_1RTT_Handshake]
-RNG_Pool = cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece749af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005
+Client_RNG_Pool = cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece749af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005
+Server_RNG_Pool = a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e26928b1580eeadf6dd589b8ef4f2d5652578cc810e9980191ec8d058308cea216a21efad6aac5
 CurrentTimestamp = 1651826546000
-ClientHello_1 = 16030100c4010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
-ServerHello = 160303005a020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304
-ServerHandshakeMessages = 17030302a2d1ff334a56f5bff6594a07cc87b580233f500f45e489e7f33af35edf7869fcf40aa40aa2b8ea73f848a7ca07612ef9f945cb960b4068905123ea78b111b429ba9191cd05d2a389280f526134aadc7fc78c4b729df828b5ecf7b13bd9aefb0e57f271585b8ea9bb355c7c79020716cfb9b1183ef3ab20e37d57a6b9d7477609aee6e122a4cf51427325250c7d0e509289444c9b3a648f1d71035d2ed65b0e3cdd0cbae8bf2d0b227812cbb360987255cc744110c453baa4fcd610928d809810e4b7ed1a8fd991f06aa6248204797e36a6a73b70a2559c09ead686945ba246ab66e5edd8044b4c6de3fcf2a89441ac66272fd8fb330ef8190579b3684596c960bd596eea520a56a8d650f563aad27409960dca63d3e688611ea5e22f4415cf9538d51a200c27034272968a264ed6540c84838d89f72c24461aad6d26f59ecaba9acbbb317b66d902f4f292a36ac1b639c637ce343117b659622245317b49eeda0c6258f100d7d961ffb138647e92ea330faeea6dfa31c7a84dc3bd7e1b7a6c7178af36879018e3f252107f243d243dc7339d5684c8b0378bf30244da8c87c843f5e56eb4c5e8280a2b48052cf93b16499a66db7cca71e4599426f7d461e66f99882bd89fc50800becca62d6c74116dbd2972fda1fa80f85df881edbe5a37668936b335583b599186dc5c6918a396fa48a181d6b6fa4f9d62d513afbb992f2b992f67f8afe67f76913fa388cb5630c8ca01e0c65d11c66a1e2ac4c85977b7c7a6999bbf10dc35ae69f5515614636c0b9b68c19ed2e31c0b3b66763038ebba42f3b38edc0399f3a9f23faa63978c317fc9fa66a73f60f0504de93b5b845e275592c12335ee340bbc4fddd502784016e4b3be7ef04dda49f4b440a30cb5d2af939828fd4ae3794e44f94df5a631ede42c1719bfdabf0253fe5175be898e750edc53370d2b
-ClientFinished = 170303003575ec4dc238cce60b298044a71e219c56cc77b0517fe9b93c7a4bfc44d87f38f80338ac98fc46deb384bd1caeacab6867d726c40546
-NewSessionTicket = 17030300de3a6b8f90414a97d6959c3487680de5134a2b240e6cffac116e95d41d6af8f6b580dcf3d11d63c758db289a015940252f55713e061dc13e078891a38efbcf5753ad8ef170ad3c7353d16d9da773b9ca7f2b9fa1b6c0d4a3d03f75e09c30ba1e62972ac46f75f7b981be63439b2999ce13064615139891d5e4c5b406f16e3fc181a77ca475840025db2f0a77f81b5ab05b94c01346755f69232c86519d86cbeeac87aac347d143f9605d64f650db4d023e70e952ca49fe5137121c74bc2697687e248746d6df353005f3bce18696129c8153556b3b6c6779b37bf15985684f
+Record_ClientHello_1 = 16030100c4010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2ef6283024dece7000006130113031302010000910000000b0009000006736572766572ff01000100000a00140012001d0017001800190100010101020103010400230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c06e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Record_ServerHello = 160303005a020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304
+Message_ServerHello = 020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdbf7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304
+Record_ServerHandshakeMessages = 17030302a2d1ff334a56f5bff6594a07cc87b580233f500f45e489e7f33af35edf7869fcf40aa40aa2b8ea73f848a7ca07612ef9f945cb960b4068905123ea78b111b429ba9191cd05d2a389280f526134aadc7fc78c4b729df828b5ecf7b13bd9aefb0e57f271585b8ea9bb355c7c79020716cfb9b1183ef3ab20e37d57a6b9d7477609aee6e122a4cf51427325250c7d0e509289444c9b3a648f1d71035d2ed65b0e3cdd0cbae8bf2d0b227812cbb360987255cc744110c453baa4fcd610928d809810e4b7ed1a8fd991f06aa6248204797e36a6a73b70a2559c09ead686945ba246ab66e5edd8044b4c6de3fcf2a89441ac66272fd8fb330ef8190579b3684596c960bd596eea520a56a8d650f563aad27409960dca63d3e688611ea5e22f4415cf9538d51a200c27034272968a264ed6540c84838d89f72c24461aad6d26f59ecaba9acbbb317b66d902f4f292a36ac1b639c637ce343117b659622245317b49eeda0c6258f100d7d961ffb138647e92ea330faeea6dfa31c7a84dc3bd7e1b7a6c7178af36879018e3f252107f243d243dc7339d5684c8b0378bf30244da8c87c843f5e56eb4c5e8280a2b48052cf93b16499a66db7cca71e4599426f7d461e66f99882bd89fc50800becca62d6c74116dbd2972fda1fa80f85df881edbe5a37668936b335583b599186dc5c6918a396fa48a181d6b6fa4f9d62d513afbb992f2b992f67f8afe67f76913fa388cb5630c8ca01e0c65d11c66a1e2ac4c85977b7c7a6999bbf10dc35ae69f5515614636c0b9b68c19ed2e31c0b3b66763038ebba42f3b38edc0399f3a9f23faa63978c317fc9fa66a73f60f0504de93b5b845e275592c12335ee340bbc4fddd502784016e4b3be7ef04dda49f4b440a30cb5d2af939828fd4ae3794e44f94df5a631ede42c1719bfdabf0253fe5175be898e750edc53370d2b
+Server_MessageToSign = 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544c5320312e332c2073657276657220436572746966696361746556657269667900764d6632b3c35c3f3205e3499ac3edbaabb88295fba751461d3678e2e5ea0687
+Server_MessageSignature = 5a747c5d88fa9bd2e55ab085a61015b7211f824cd484145ab3ff52f1fda8477b0b7abc90db78e2d33a5c141a078653fa6bef780c5ea248eeaaa785c4f394cab6d30bbe8d4859ee511f602957b15411ac027671459e46445c9ea58c181e818e95b8c3fb0bf3278409d3be152a3da5043e063dda65cdf5aea20d53dfacd42f74f3
+Message_EncryptedExtensions = 080000240022000a00140012001d00170018001901000101010201030104001c0002400100000000
+Message_Server_Certificate = 0b0001b9000001b50001b0308201ac30820115a003020102020102300d06092a864886f70d01010b0500300e310c300a06035504031303727361301e170d3136303733303031323335395a170d3236303733303031323335395a300e310c300a0603550403130372736130819f300d06092a864886f70d010101050003818d0030818902818100b4bb498f8279303d980836399b36c6988c0c68de55e1bdb826d3901a2461eafd2de49a91d015abbc9a95137ace6c1af19eaa6af98c7ced43120998e187a80ee0ccb0524b1b018c3e0b63264d449a6d38e22a5fda430846748030530ef0461c8ca9d9efbfae8ea6d1d03e2bd193eff0ab9a8002c47428a6d35a8d88d79f7f1e3f0203010001a31a301830090603551d1304023000300b0603551d0f0404030205a0300d06092a864886f70d01010b05000381810085aad2a0e5b9276b908c65f73a7267170618a54c5f8a7b337d2df7a594365417f2eae8f8a58c8f8172f9319cf36b7fd6c55b80f21a03015156726096fd335e5e67f2dbf102702e608ccae6bec1fc63a42a99be5c3eb7107c3c54e9b9eb2bd5203b1c3b84e0a8b2f759409ba3eac9d91d402dcc0cc8f8961229ac9187b42b4de10000
+Message_Server_CertificateVerify = 0f000084080400805a747c5d88fa9bd2e55ab085a61015b7211f824cd484145ab3ff52f1fda8477b0b7abc90db78e2d33a5c141a078653fa6bef780c5ea248eeaaa785c4f394cab6d30bbe8d4859ee511f602957b15411ac027671459e46445c9ea58c181e818e95b8c3fb0bf3278409d3be152a3da5043e063dda65cdf5aea20d53dfacd42f74f3
+Message_Server_Finished = 140000209b9b141d906337fbd2cbdce71df4deda4ab42c309572cb7fffee5454b78f0718
+Record_ClientFinished = 170303003575ec4dc238cce60b298044a71e219c56cc77b0517fe9b93c7a4bfc44d87f38f80338ac98fc46deb384bd1caeacab6867d726c40546
+Record_NewSessionTicket = 17030300de3a6b8f90414a97d6959c3487680de5134a2b240e6cffac116e95d41d6af8f6b580dcf3d11d63c758db289a015940252f55713e061dc13e078891a38efbcf5753ad8ef170ad3c7353d16d9da773b9ca7f2b9fa1b6c0d4a3d03f75e09c30ba1e62972ac46f75f7b981be63439b2999ce13064615139891d5e4c5b406f16e3fc181a77ca475840025db2f0a77f81b5ab05b94c01346755f69232c86519d86cbeeac87aac347d143f9605d64f650db4d023e70e952ca49fe5137121c74bc2697687e248746d6df353005f3bce18696129c8153556b3b6c6779b37bf15985684f
 SessionTicket = 308202d5020401348a5902046274df7202010302010404000481b22c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357020213010201000201010201000101ff01010004204ecd0eb6ec3b4d87f5d6028f922ca4c5851a277fd41311c9e62d2c9492e1c4f3048201b0308201ac30820115a003020102020102300d06092a864886f70d01010b0500300e310c300a06035504031303727361301e170d3136303733303031323335395a170d3236303733303031323335395a300e310c300a0603550403130372736130819f300d06092a864886f70d010101050003818d0030818902818100b4bb498f8279303d980836399b36c6988c0c68de55e1bdb826d3901a2461eafd2de49a91d015abbc9a95137ace6c1af19eaa6af98c7ced43120998e187a80ee0ccb0524b1b018c3e0b63264d449a6d38e22a5fda430846748030530ef0461c8ca9d9efbfae8ea6d1d03e2bd193eff0ab9a8002c47428a6d35a8d88d79f7f1e3f0203010001a31a301830090603551d1304023000300b0603551d0f0404030205a0300d06092a864886f70d01010b05000381810085aad2a0e5b9276b908c65f73a7267170618a54c5f8a7b337d2df7a594365417f2eae8f8a58c8f8172f9319cf36b7fd6c55b80f21a03015156726096fd335e5e67f2dbf102702e608ccae6bec1fc63a42a99be5c3eb7107c3c54e9b9eb2bd5203b1c3b84e0a8b2f759409ba3eac9d91d402dcc0cc8f8961229ac9187b42b4de10c067365727665720c000201000c000201000101ff02020400020500fad6aac502011e
 Client_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031
-Client_AppData_Record = 1703030043a23f7054b62c94d0affafe8228ba55cbefacea42f914aa66bcab3f2b9819a8a5b46b395bd54a9a20441e2b62974e1f5a6292a2977014bd1e3deae63aeebb21694915e4
+Record_Client_AppData = 1703030043a23f7054b62c94d0affafe8228ba55cbefacea42f914aa66bcab3f2b9819a8a5b46b395bd54a9a20441e2b62974e1f5a6292a2977014bd1e3deae63aeebb21694915e4
 Server_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031
-Server_AppData_Record = 17030300432e937e11ef4ac740e538ad36005fc4a46932fc3225d05f82aa1b36e30efaf97d90e6dffc602dcb501a59a8fcc49c4bf2e5f0a21c0047c2abf332540dd032e167c2955d
-Client_CloseNotify = 1703030013c9872760655666b74d7ff1153efd6db6d0b0e3
-Server_CloseNotify = 1703030013b58fd67166ebf599d24720cfbe7efa7a8864a9
+Record_Server_AppData = 17030300432e937e11ef4ac740e538ad36005fc4a46932fc3225d05f82aa1b36e30efaf97d90e6dffc602dcb501a59a8fcc49c4bf2e5f0a21c0047c2abf332540dd032e167c2955d
+Record_Client_CloseNotify = 1703030013c9872760655666b74d7ff1153efd6db6d0b0e3
+Record_Server_CloseNotify = 1703030013b58fd67166ebf599d24720cfbe7efa7a8864a9
 
 [Resumed_0RTT_Handshake]
-RNG_Pool = 1bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d95bff91188283846dd6a2134ef7180ca2b0b14fb10dce707b5098c0dddc813b2df
+Client_RNG_Pool = 1bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d95bff91188283846dd6a2134ef7180ca2b0b14fb10dce707b5098c0dddc813b2df
+Server_RNG_Pool = 3ccfd2dec890222763472ae8136777c9d7358777bb66e91ea5122495f559ea2d
 CurrentTimestamp = 1651826546006
 SessionTicket = 308202d5020401348a5902046274df7202010302010404000481b22c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357020213010201000201010201000101ff01010004204ecd0eb6ec3b4d87f5d6028f922ca4c5851a277fd41311c9e62d2c9492e1c4f3048201b0308201ac30820115a003020102020102300d06092a864886f70d01010b0500300e310c300a06035504031303727361301e170d3136303733303031323335395a170d3236303733303031323335395a300e310c300a0603550403130372736130819f300d06092a864886f70d010101050003818d0030818902818100b4bb498f8279303d980836399b36c6988c0c68de55e1bdb826d3901a2461eafd2de49a91d015abbc9a95137ace6c1af19eaa6af98c7ced43120998e187a80ee0ccb0524b1b018c3e0b63264d449a6d38e22a5fda430846748030530ef0461c8ca9d9efbfae8ea6d1d03e2bd193eff0ab9a8002c47428a6d35a8d88d79f7f1e3f0203010001a31a301830090603551d1304023000300b0603551d0f0404030205a0300d06092a864886f70d01010b05000381810085aad2a0e5b9276b908c65f73a7267170618a54c5f8a7b337d2df7a594365417f2eae8f8a58c8f8172f9319cf36b7fd6c55b80f21a03015156726096fd335e5e67f2dbf102702e608ccae6bec1fc63a42a99be5c3eb7107c3c54e9b9eb2bd5203b1c3b84e0a8b2f759409ba3eac9d91d402dcc0cc8f8961229ac9187b42b4de10c067365727665720c000201000c000201000101ff02020400020500fad6aac502011e
-ClientHello_1 = 1603010200010001fc03031bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d95000006130113031302010001cd0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d0020e4ffb68ac05f8d96c99da26698346c6be16482badddafe051a66b4f18d668f0b002a0000002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c0002400100150057000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002900dd00b800b22c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357fad6aacb0021203add4fb2d8fdf822a0ca3cf7678ef5e88dae990141c5924d57bb6fa31b9e5f9d
+Record_ClientHello_1 = 1603010200010001fc03031bc3ceb6bbe39cff938355b5a50adb6db21b7a6af649d7b4bc419d7876487d95000006130113031302010001cd0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d0020e4ffb68ac05f8d96c99da26698346c6be16482badddafe051a66b4f18d668f0b002a0000002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c0002400100150057000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002900dd00b800b22c035d829359ee5ff7af4ec900000000262a6494dc486d2c8a34cb33fa90bf1b0070ad3c498883c9367c09a2be785abc55cd226097a3a982117283f82a03a143efd3ff5dd36d64e861be7fd61d2827db279cce145077d454a3664d4e6da4d29ee03725a6a4dafcd0fc67d2aea70529513e3da2677fa5906c5b3f7d8f92f228bda40dda721470f9fbf297b5aea617646fac5c03272e970727c621a79141ef5f7de6505e5bfbc388e93343694093934ae4d357fad6aacb0021203add4fb2d8fdf822a0ca3cf7678ef5e88dae990141c5924d57bb6fa31b9e5f9d
 Client_EarlyAppData = 414243444546
-Client_EarlyAppData_Record = 1703030017ab1df420e75c457a7cc5d2844f76d5aee4b4edbf049be0
-ServerHello = 16030300600200005c03033ccfd2dec890222763472ae8136777c9d7358777bb66e91ea5122495f559ea2d00130100003400290002000000330024001d0020121761ee42c333e1b9e77b60dd57c2053cd94512ab47f115e86eff50942cea31002b00020304
-ServerHandshakeMessages = 1703030061dc48237b4b879f50d0d4d262ea8b4716eb40ddc1eb957e11126e8a7149c2d012d37a7115957e64ce30008b9e0323f2c05a9c1c77b4f37849a695ab255060a33fee770ca95cb8486bfd0843b87024865ca35cc41c4e515c64dcb1369f98635bc7a5
+Record_Client_EarlyAppData = 1703030017ab1df420e75c457a7cc5d2844f76d5aee4b4edbf049be0
+Record_ServerHello = 16030300600200005c03033ccfd2dec890222763472ae8136777c9d7358777bb66e91ea5122495f559ea2d00130100003400290002000000330024001d0020121761ee42c333e1b9e77b60dd57c2053cd94512ab47f115e86eff50942cea31002b00020304
+Record_ServerHandshakeMessages = 1703030061dc48237b4b879f50d0d4d262ea8b4716eb40ddc1eb957e11126e8a7149c2d012d37a7115957e64ce30008b9e0323f2c05a9c1c77b4f37849a695ab255060a33fee770ca95cb8486bfd0843b87024865ca35cc41c4e515c64dcb1369f98635bc7a5
 # ClientFinished contains two records:
 #  * end of early data
 #  * client finished
-ClientFinished = 1703030015aca6fc944841298df99593725f9bf9754429b12f09170303003500f8b467d14cf22a4b3f0b6ae0d8e6cc8d08e0db3515ef5c2bdf1922eafbb70009964716d834fb70c3d2a56c5b1f5f6bdba6c333cf
+Record_ClientFinished = 1703030015aca6fc944841298df99593725f9bf9754429b12f09170303003500f8b467d14cf22a4b3f0b6ae0d8e6cc8d08e0db3515ef5c2bdf1922eafbb70009964716d834fb70c3d2a56c5b1f5f6bdba6c333cf
 Client_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031
-Client_AppData_Record = 1703030043b1cebce242aa201be9ae5e1cb2a9aa4b33d4e866af1edb068919237741aa031d7a74d491c99b9d4e232b74206bc6fbaa04fe78be44a9b4f54320a17eb76992afac3103
+Record_Client_AppData = 1703030043b1cebce242aa201be9ae5e1cb2a9aa4b33d4e866af1edb068919237741aa031d7a74d491c99b9d4e232b74206bc6fbaa04fe78be44a9b4f54320a17eb76992afac3103
 Server_AppData = 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031
-Server_AppData_Record = 1703030043275e9f20acff57bc000657d3867df039cccf79047884cf75771746f740b5a83f462a0954c3581393a203a25a7dd14141ef1a37900cdb62ff62dee1ba39ab2590cbf194
-Client_CloseNotify = 17030300130facce3246bdfc6369838d6a82ae6de5d422dc
-Server_CloseNotify = 17030300135b18af444e8e1eec7158fb62d8f2577d37ba5d
+Record_Server_AppData = 1703030043275e9f20acff57bc000657d3867df039cccf79047884cf75771746f740b5a83f462a0954c3581393a203a25a7dd14141ef1a37900cdb62ff62dee1ba39ab2590cbf194
+Record_Client_CloseNotify = 17030300130facce3246bdfc6369838d6a82ae6de5d422dc
+Record_Server_CloseNotify = 17030300135b18af444e8e1eec7158fb62d8f2577d37ba5d
 
 [HelloRetryRequest_Handshake]
-RNG_Pool = b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f0ed02f8e8117efc75ca7ac32aa7e34eda64cdc0ddad154a5e85289f959f63204ab5473467e19346ceb0a0414e41da21d4d2445bc3025afe97c4e8dc8d513da39
+Client_RNG_Pool = b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f0ed02f8e8117efc75ca7ac32aa7e34eda64cdc0ddad154a5e85289f959f63204ab5473467e19346ceb0a0414e41da21d4d2445bc3025afe97c4e8dc8d513da39
+Server_RNG_Pool = bb341d847fd789c47c387172dc0c9bf147fccacb5043d86ca4c598d3ff571b988c510601f9765bfb8ed693449a48989859b5cfa879cb9f5443c41c5ff10634ed
 CurrentTimestamp = 1651826546000
-ClientHello_1 = 16030100b4010000b00303b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f000006130113031302010000810000000b0009000006736572766572ff01000100000a00080006001d00170018003300260024001d0020e8e8e3f3b93a25ed97a14a7dcacb8a272c6288e585c6484d05262fcad062ad1f002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
-HelloRetryRequest = 16030300b0020000ac0303cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c001301000084003300020017002c0074007271dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0002b00020304
-ClientHello_2 = 1603030200010001fc0303b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f000006130113031302010001cd0000000b0009000006736572766572ff01000100000a00080006001d001700180033004700450017004104a6da7392ec591e17abfd535964b99894d13befb221b3def2ebe3830eac8f0151812677c4d6d2237e85cf01d6910cfb83954e76ba7352830534159897e8065780002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002c0074007271dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0002d00020101001c00024001001500af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
-ServerHello = 160303007b020000770303bb341d847fd789c47c387172dc0c9bf147fccacb5043d86ca4c598d3ff571b9800130100004f003300450017004104583e054b7a66672ae020ad9d2686fcc85b5ad41a134a0f03ee72b893052bd85b4c8de6776f5b04ac07d83540eab3e3d9c547bc6528c4317d294686093a6cad7d002b00020304
-ServerHandshakeMessages = 170303029699bee20baf5b7fc727bfab6223928a381e6d0cf9c4da653f9d2a7b23f7de11cce842d5cf75631763450ffb8b0cc1d238e658af7a12adc86243114ab14a1da2fae42621ce483fb6242eabfaad52566b02b31d2eddedefeb80e66a9900d5f973b40c4fdf74719ecf1b68d7f9c3b6ceb903ca13dd1bb8f8187ae33417e1d152522c5822a1a03ad52c838c55953d610222874cce8e1790b229a2aa0b53c8d377ee720182951dc6181dc5d90bd1f0105ed1e84aa5f75957c6661897079e5ea5007449e3197bdc7c9beeedddeafdd844afa5c315ecfe65e576afe909812880620ec7048b42d7f5c78d76f299d6d82534bdd8f512febc0ed3814aca470cd8000d3e1cb9962b052fbb950df683a52c2ba77ed3713b122937a6e5170964e2ab7969dcd980b3db9b458da7603124d6dc005e4d6e04b4d0c4baf3275db827dbba0a6db09672171fc057b3851d7e026841e2978fbd2346bbefdd0376bb1108fe9acc92189f5650aa5e85d8e8c7b67ac510dba003d3d7e16350bb66d45013efd44c9b607c0d318c4c7d1a1f5cbc57e20611804e3787d7b4a4b5f08ed8fd70bdaeade02260b12ab842ef690b4a3ee7911e841b374ecd5ebbbc2a54d047b600336dd7d0c88b4bc10e58ee6cb656de7247fa20d8e91deb84628608cf80615b62e96c1491c7ac3755eb6901405d3474fe1ac79d106a0cee56c2577fc88480f96cb6b8c681b7b68b53c146093908f350888175bdfb0b1e31ad61e30ba0adfe6d223aa03c0783b5001a57587c328a9afcfcfb978d1cd4328f7d9d60530e630befd96c0c816ee20b0100768ae2a6df51fc68f172740a79af11398ee3be1252491fa9c693479e877f94ab7c5f8cad480203e6ab7b87dd71e8a0729113df17f5eee86ce108d1d72007ec1cd13c85a6c149621e77b7d78d805a30f0be030c315e54
-ClientFinished = 1703030035d74f1923c662fd34137c6f502f3dd2b93d951d1b3bc97e42afe23c31abea92fe91b474999e85e3b791ce252fe8c3e9f939a4120cb2
-Client_CloseNotify = 17030300132ea6cdf749196023e2b3a49491695536426047
-Server_CloseNotify = 1703030013519fc5075cb0884349759ff9ef6f011bb4c6f2
+HelloRetryRequest_Cookie = 71dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0
+Server_MessageToSign = 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544C5320312E332C2073657276657220436572746966696361746556657269667900A303FD397938C2B5C3FB0DFB33BD6C46D890331E3BE5ED3B8EA2BE5218C3DB1A
+Server_MessageSignature = 33ab13d4462707231b5dcae6c8190b63d1dabc74f28c395370da0b07e5b83066d0246a31acd95df475bfd799a4a70d33ad93d3a317a9b2c0d237a5685b219e774112e391a247607d1aeff1bbd0a39f382ee1a5fe88ae99ec59228e6497e45d48ce275a6d5ef40d169fb6f9d33b052ed3dcdd6b5a48baafffbcb290128415bd38
+Record_ClientHello_1 = 16030100b4010000b00303b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f000006130113031302010000810000000b0009000006736572766572ff01000100000a00080006001d00170018003300260024001d0020e8e8e3f3b93a25ed97a14a7dcacb8a272c6288e585c6484d05262fcad062ad1f002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Record_HelloRetryRequest = 16030300b0020000ac0303cf21ad74e59a6111be1d8c021e65b891c2a211167abb8c5e079e09e2c8a8339c001301000084003300020017002c0074007271dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0002b00020304
+Record_ClientHello_2 = 1603030200010001fc0303b0b1c5a5aa37c5919f2ed1d5c6fff7fcb7849716945a2b8cee9258a346677b6f000006130113031302010001cd0000000b0009000006736572766572ff01000100000a00080006001d001700180033004700450017004104a6da7392ec591e17abfd535964b99894d13befb221b3def2ebe3830eac8f0151812677c4d6d2237e85cf01d6910cfb83954e76ba7352830534159897e8065780002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002c0074007271dcd04bb88bc3189119398a00000000eefafc76c146b823b096f8aacad365dd0030953f4edf625636e5f21bb2e23fcc654b1b5b40318d10d137abcbb87574e36e8a1f025f7dfa5d6e50781b5eda4aa15b0c8be778257d16aa3030e9e7841dd9e4c0342267e8ca0caf571fb2b7cff0f934b0002d00020101001c00024001001500af00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+Record_ServerHello = 160303007b020000770303bb341d847fd789c47c387172dc0c9bf147fccacb5043d86ca4c598d3ff571b9800130100004f003300450017004104583e054b7a66672ae020ad9d2686fcc85b5ad41a134a0f03ee72b893052bd85b4c8de6776f5b04ac07d83540eab3e3d9c547bc6528c4317d294686093a6cad7d002b00020304
+Message_ServerHello = 020000770303bb341d847fd789c47c387172dc0c9bf147fccacb5043d86ca4c598d3ff571b9800130100004f003300450017004104583e054b7a66672ae020ad9d2686fcc85b5ad41a134a0f03ee72b893052bd85b4c8de6776f5b04ac07d83540eab3e3d9c547bc6528c4317d294686093a6cad7d002b00020304
+Record_ServerHandshakeMessages = 170303029699bee20baf5b7fc727bfab6223928a381e6d0cf9c4da653f9d2a7b23f7de11cce842d5cf75631763450ffb8b0cc1d238e658af7a12adc86243114ab14a1da2fae42621ce483fb6242eabfaad52566b02b31d2eddedefeb80e66a9900d5f973b40c4fdf74719ecf1b68d7f9c3b6ceb903ca13dd1bb8f8187ae33417e1d152522c5822a1a03ad52c838c55953d610222874cce8e1790b229a2aa0b53c8d377ee720182951dc6181dc5d90bd1f0105ed1e84aa5f75957c6661897079e5ea5007449e3197bdc7c9beeedddeafdd844afa5c315ecfe65e576afe909812880620ec7048b42d7f5c78d76f299d6d82534bdd8f512febc0ed3814aca470cd8000d3e1cb9962b052fbb950df683a52c2ba77ed3713b122937a6e5170964e2ab7969dcd980b3db9b458da7603124d6dc005e4d6e04b4d0c4baf3275db827dbba0a6db09672171fc057b3851d7e026841e2978fbd2346bbefdd0376bb1108fe9acc92189f5650aa5e85d8e8c7b67ac510dba003d3d7e16350bb66d45013efd44c9b607c0d318c4c7d1a1f5cbc57e20611804e3787d7b4a4b5f08ed8fd70bdaeade02260b12ab842ef690b4a3ee7911e841b374ecd5ebbbc2a54d047b600336dd7d0c88b4bc10e58ee6cb656de7247fa20d8e91deb84628608cf80615b62e96c1491c7ac3755eb6901405d3474fe1ac79d106a0cee56c2577fc88480f96cb6b8c681b7b68b53c146093908f350888175bdfb0b1e31ad61e30ba0adfe6d223aa03c0783b5001a57587c328a9afcfcfb978d1cd4328f7d9d60530e630befd96c0c816ee20b0100768ae2a6df51fc68f172740a79af11398ee3be1252491fa9c693479e877f94ab7c5f8cad480203e6ab7b87dd71e8a0729113df17f5eee86ce108d1d72007ec1cd13c85a6c149621e77b7d78d805a30f0be030c315e54
+Message_EncryptedExtensions = 080000180016000a0008000600170018001d001c0002400100000000
+Message_Server_Certificate = 0b0001b9000001b50001b0308201ac30820115a003020102020102300d06092a864886f70d01010b0500300e310c300a06035504031303727361301e170d3136303733303031323335395a170d3236303733303031323335395a300e310c300a0603550403130372736130819f300d06092a864886f70d010101050003818d0030818902818100b4bb498f8279303d980836399b36c6988c0c68de55e1bdb826d3901a2461eafd2de49a91d015abbc9a95137ace6c1af19eaa6af98c7ced43120998e187a80ee0ccb0524b1b018c3e0b63264d449a6d38e22a5fda430846748030530ef0461c8ca9d9efbfae8ea6d1d03e2bd193eff0ab9a8002c47428a6d35a8d88d79f7f1e3f0203010001a31a301830090603551d1304023000300b0603551d0f0404030205a0300d06092a864886f70d01010b05000381810085aad2a0e5b9276b908c65f73a7267170618a54c5f8a7b337d2df7a594365417f2eae8f8a58c8f8172f9319cf36b7fd6c55b80f21a03015156726096fd335e5e67f2dbf102702e608ccae6bec1fc63a42a99be5c3eb7107c3c54e9b9eb2bd5203b1c3b84e0a8b2f759409ba3eac9d91d402dcc0cc8f8961229ac9187b42b4de10000
+Message_Server_CertificateVerify = 0f0000840804008033ab13d4462707231b5dcae6c8190b63d1dabc74f28c395370da0b07e5b83066d0246a31acd95df475bfd799a4a70d33ad93d3a317a9b2c0d237a5685b219e774112e391a247607d1aeff1bbd0a39f382ee1a5fe88ae99ec59228e6497e45d48ce275a6d5ef40d169fb6f9d33b052ed3dcdd6b5a48baafffbcb290128415bd38
+Message_Server_Finished = 140000208863e6bfb0420a927fa27f34336a70ae426e968e3eb884945b96856dba3976d1
+Record_ClientFinished = 1703030035d74f1923c662fd34137c6f502f3dd2b93d951d1b3bc97e42afe23c31abea92fe91b474999e85e3b791ce252fe8c3e9f939a4120cb2
+Record_Client_CloseNotify = 17030300132ea6cdf749196023e2b3a49491695536426047
+Record_Server_CloseNotify = 1703030013519fc5075cb0884349759ff9ef6f011bb4c6f2
 
 [Client_Authentication_Handshake]
-RNG_Pool = 6a472236328b83af40386d3a3e1f1ce624fa4ed89ab865a4ff0f4144ce3ae233c040b2bb8f3addd20fd4058c547003a3c6f9c1cd915d5e535c87d8d191aaf071
+Client_RNG_Pool = 6a472236328b83af40386d3a3e1f1ce624fa4ed89ab865a4ff0f4144ce3ae233c040b2bb8f3addd20fd4058c547003a3c6f9c1cd915d5e535c87d8d191aaf071
+Server_RNG_Pool = 3b50fdf1c3d572e40e68953e7fff4e2758459c59afa0582c0ea032874255fe6e
 CurrentTimestamp = 1651826546000
-ClientHello_1 = 16030100c0010000bc03036a472236328b83af40386d3a3e1f1ce624fa4ed89ab865a4ff0f4144ce3ae2330000061301130313020100008d0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d0020089cc2671f738d9a671e5b2e464981d05b76e361aa22aea91f1d49ca10a7a362002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
-ServerHello = 160303005a0200005603033b50fdf1c3d572e40e68953e7fff4e2758459c59afa0582c0ea032874255fe6e00130100002e00330024001d00206c2e50e865919a6b5a12dfaf918f92b442567b0f89bc54478c6921366658f062002b00020304
-ServerHandshakeMessages = 17030302166d0a7ac079b32a94aa68c4e2893e8bd0d3c185f549c236fbbce3d647f08f3c94a2bf424d8708883605ad8955f97718b0213dead13dfb23ebb8381da582756612bcb5a5d40847719fbe9f179bfae656f3ecfd59a4c0d35132ce418a7e46f6b6a60622f8a6c06b28d83360163563be9c37f97eb902326924a72b3ed8c8381277d1581cab9c3715ac2401398467ad7ebfab3d0c3419e750104f7d62c5027901f2e4cd4ca5b8071eb03d3c732d83215066dfc4d291d4c1ff3b8d7e4298f677d4d51dea1168d8f16cb27ba40266313a1fedf9e23cc77f765450f9e96f05d08f3da245b14d4946f07ec81eed6d56f26bd574f0b7f7c7047037c16fce3b23754e662fad73e2b7213f6af296769c99a1d38e6232e0ec8dc4f84d6aa6f7de3887be0057862f9018e0ab396705aa4090ab5f2dff6325a557e7320d4effd46bb4f997d163207cce6665294aa4465541e3fe37ee7350659ea550d6dcb6af3c518852c7a14c3cc15bc32b3273bdf1751da184203135b117d300204fb12d58ca9ac34b68eca27030832f7a4b46d2a55757f63fe8f6e85ac47469e6198da88a64586bf23c69590de822263be75fd836847240c48f8c145cd6bd698962e7edc234ebe59231351eef8d7652cf3b08ab3af6e5ec74c58a8da34b39f9b0d6c4279a9a1f82071729e7059dd7f7b95b9433c4684ce1891a6d33432d52eddb0b8cee9181d403eccc12991f1ad4aa62c36049713a7bb135fdda6661a05a93f8c16f
-MessageToSign = 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544c5320312e332c20636c69656e742043657274696669636174655665726966790044026ef3331bc864da1c08ee32e196d93cc2c5d487e47e957d980e7c3d801b9a
-MessageSignature = 186b2223b503a759c35dba0e9721b4b579138d5f0f5e6ec7feaaf27f3ad7f386c2c7bd7cb2be52fbf5ed8393f406ee79369692ec7ac695651d858219e672a8eb7b2a677b640b46ab630edc5f3f2f8272b9c0d906f81f84ddc5b8c7bcf955c78a3cf99e5016f73e04eb7dfcb28833f13e8f75ec2ff3581e2f098ad4157fd6d6ad
-ClientFinished = 1703030280b46a63934e673841abaf267403bc677f6b6d2a1e2f12bb5f62683bfe36a82673f06d6287ddd609bcf2f5fd3225923d24af3c76682c180ee571a17ca4bfbe2f510dc9a0e1fca5cff2cee87d11cb531a6ef90bf5309a6b63bbbc0b88ea45103a4304091543859fa11ec032ed873444cd5185ead5f6a76420f0f0286acef802c8e4788c23275f1b06da600f4a7decd0bc59d7bef10e649ae32690397fc3d4ed6f30f801d8cd569b71ad4fa05ea7cf2ac2dfa150d220505d4011b34d09d53853eba61a101e4f8dca47d8171a884b19259a3dd48c5ac141983edc77814d25e7f66bbbdb9096839266e06561828ecfb27eafd4e9e81a0b96e3bfa42dae5ad80359b9a6661402c3a2104177030106dbd8f65bb6a0159d512eb13af22a259f313bd58c2e21fe053d57f2a962b0a4ea682c96f70b79b560136192823b27be6a2fb7b1c751ccc0e3303615541485b7b307b423332c11efa80b72f9b80a53e53f7bb38a3af4c59f8008bad0544e5614e688ff57bccd6935f81f447f420c1c1bf4058818e90bf5dc716ccae42524856df8250bcdbd7af65f82dd53061d024f6d2ff5c11e3792a9a70e0ee2a3c20a1b968ac391f8f92831135d25242ada2fe241c2653ec996339dfa12dfae7a3373df88b07ca27aef6dc266a25f13f75c76039c1f46fd7a53ae6399c999f4b2aee18e480d6d12bfae226bbdc92a6ad50b4d3bac7abc3b3651eb5be56f33bf41127b3ca886dc714a50d1490357bd40d9fd6be42209a4ddb9ebb2987e29f120f05814614d2c79320015b461fe7324447670a1af5f65caed15b474ab7faa495016adf808e53b94ef54afbb0e0a3a2732ab597f7d5923c77386aa5124731f8cc73e703b341c175a454939a77ab64313c15cf3fe03c4f33842564976
-Client_CloseNotify = 1703030013e4ad7d44c29245339d355962c779b89ef44c58
-Server_CloseNotify = 17030300131decc5d6e64bba8a6f21b4fd077497da2a90cb
+Record_ClientHello_1 = 16030100c0010000bc03036a472236328b83af40386d3a3e1f1ce624fa4ed89ab865a4ff0f4144ce3ae2330000061301130313020100008d0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d0020089cc2671f738d9a671e5b2e464981d05b76e361aa22aea91f1d49ca10a7a362002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Record_ServerHello = 160303005a0200005603033b50fdf1c3d572e40e68953e7fff4e2758459c59afa0582c0ea032874255fe6e00130100002e00330024001d00206c2e50e865919a6b5a12dfaf918f92b442567b0f89bc54478c6921366658f062002b00020304
+Record_ServerHandshakeMessages = 17030302166d0a7ac079b32a94aa68c4e2893e8bd0d3c185f549c236fbbce3d647f08f3c94a2bf424d8708883605ad8955f97718b0213dead13dfb23ebb8381da582756612bcb5a5d40847719fbe9f179bfae656f3ecfd59a4c0d35132ce418a7e46f6b6a60622f8a6c06b28d83360163563be9c37f97eb902326924a72b3ed8c8381277d1581cab9c3715ac2401398467ad7ebfab3d0c3419e750104f7d62c5027901f2e4cd4ca5b8071eb03d3c732d83215066dfc4d291d4c1ff3b8d7e4298f677d4d51dea1168d8f16cb27ba40266313a1fedf9e23cc77f765450f9e96f05d08f3da245b14d4946f07ec81eed6d56f26bd574f0b7f7c7047037c16fce3b23754e662fad73e2b7213f6af296769c99a1d38e6232e0ec8dc4f84d6aa6f7de3887be0057862f9018e0ab396705aa4090ab5f2dff6325a557e7320d4effd46bb4f997d163207cce6665294aa4465541e3fe37ee7350659ea550d6dcb6af3c518852c7a14c3cc15bc32b3273bdf1751da184203135b117d300204fb12d58ca9ac34b68eca27030832f7a4b46d2a55757f63fe8f6e85ac47469e6198da88a64586bf23c69590de822263be75fd836847240c48f8c145cd6bd698962e7edc234ebe59231351eef8d7652cf3b08ab3af6e5ec74c58a8da34b39f9b0d6c4279a9a1f82071729e7059dd7f7b95b9433c4684ce1891a6d33432d52eddb0b8cee9181d403eccc12991f1ad4aa62c36049713a7bb135fdda6661a05a93f8c16f
+Client_MessageToSign = 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544c5320312e332c20636c69656e742043657274696669636174655665726966790044026ef3331bc864da1c08ee32e196d93cc2c5d487e47e957d980e7c3d801b9a
+Client_MessageSignature = 186b2223b503a759c35dba0e9721b4b579138d5f0f5e6ec7feaaf27f3ad7f386c2c7bd7cb2be52fbf5ed8393f406ee79369692ec7ac695651d858219e672a8eb7b2a677b640b46ab630edc5f3f2f8272b9c0d906f81f84ddc5b8c7bcf955c78a3cf99e5016f73e04eb7dfcb28833f13e8f75ec2ff3581e2f098ad4157fd6d6ad
+Record_ClientFinished = 1703030280b46a63934e673841abaf267403bc677f6b6d2a1e2f12bb5f62683bfe36a82673f06d6287ddd609bcf2f5fd3225923d24af3c76682c180ee571a17ca4bfbe2f510dc9a0e1fca5cff2cee87d11cb531a6ef90bf5309a6b63bbbc0b88ea45103a4304091543859fa11ec032ed873444cd5185ead5f6a76420f0f0286acef802c8e4788c23275f1b06da600f4a7decd0bc59d7bef10e649ae32690397fc3d4ed6f30f801d8cd569b71ad4fa05ea7cf2ac2dfa150d220505d4011b34d09d53853eba61a101e4f8dca47d8171a884b19259a3dd48c5ac141983edc77814d25e7f66bbbdb9096839266e06561828ecfb27eafd4e9e81a0b96e3bfa42dae5ad80359b9a6661402c3a2104177030106dbd8f65bb6a0159d512eb13af22a259f313bd58c2e21fe053d57f2a962b0a4ea682c96f70b79b560136192823b27be6a2fb7b1c751ccc0e3303615541485b7b307b423332c11efa80b72f9b80a53e53f7bb38a3af4c59f8008bad0544e5614e688ff57bccd6935f81f447f420c1c1bf4058818e90bf5dc716ccae42524856df8250bcdbd7af65f82dd53061d024f6d2ff5c11e3792a9a70e0ee2a3c20a1b968ac391f8f92831135d25242ada2fe241c2653ec996339dfa12dfae7a3373df88b07ca27aef6dc266a25f13f75c76039c1f46fd7a53ae6399c999f4b2aee18e480d6d12bfae226bbdc92a6ad50b4d3bac7abc3b3651eb5be56f33bf41127b3ca886dc714a50d1490357bd40d9fd6be42209a4ddb9ebb2987e29f120f05814614d2c79320015b461fe7324447670a1af5f65caed15b474ab7faa495016adf808e53b94ef54afbb0e0a3a2732ab597f7d5923c77386aa5124731f8cc73e703b341c175a454939a77ab64313c15cf3fe03c4f33842564976
+Record_Client_CloseNotify = 1703030013e4ad7d44c29245339d355962c779b89ef44c58
+Record_Server_CloseNotify = 17030300131decc5d6e64bba8a6f21b4fd077497da2a90cb
 
 [Middlebox_Compatibility_Mode]
-RNG_Pool = 4e640a3f2c2738f09c9418bd78edccd7559d0531199276d4d92a0e9ee9d77d09a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6efdea00b45695dc781f19d34a62c1afd31ab4369af1e855a3bbb258d8442cde6d7
+Client_RNG_Pool = 4e640a3f2c2738f09c9418bd78edccd7559d0531199276d4d92a0e9ee9d77d09a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6efdea00b45695dc781f19d34a62c1afd31ab4369af1e855a3bbb258d8442cde6d7
+Server_RNG_Pool = e5dd5948c435f7a38f0f0130708dc322d9df09abd4838117c183a7bb6d994f2c017c38a3647921ca2d9ed6bd7ae7132b94211b1331bb208c8ccdd51556409995
 CurrentTimestamp = 1651826546000
-ClientHello_1 = 16030100e0010000dc03034e640a3f2c2738f09c9418bd78edccd7559d0531199276d4d92a0e9ee9d77d0920a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef00061301130313020100008d0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d00208e7292cf3056dbb0d25fcbe55c107dc9bbf83dd9708f39203ba341249a7d9b63002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
-ServerHello = 160303007a020000760303e5dd5948c435f7a38f0f0130708dc322d9df09abd4838117c183a7bb6d994f2c20a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef130100002e00330024001d00203e30f0f4ba551afd62768341175f5265e4daf0c8841617aa4fafdd2142320c22002b00020304
-ServerHandshakeMessages = 14030300010117030302a248de891d9c3624a67a6c6f0601ab7ac20c1f6a9e14d2e6007e999e130367a8af1bcfea9498fbce19df4505eece3a25da523cbe55ea1b3bda4e91995e455d500a4faa6227b7111e1c8547e2d7c179db215303d25827f3cd18f48f6491328cf5c0f814d388150bd9e9264aae491db6995069bea17665d5e0c817284d4ac21880054c3657331e23a9304dc88a15c04ec80bd3852bf7f9d3c6615b15fac83bbca031c6d2310d9f5d7a4b020a4f7c19062b65c05a1d3264b557ec9d8e0f7cee27e36f793039de8dd96edfca9009e0651034bff31d7f349eece01d99fcb5fcab840d7707c72299c3b5d04564e880a33c5e846c762e3d922bb55303d1d87cc0f06573f17dcb9b8ffd35bbd883c1cb3aa24fcc325005f768ce2fb624ca97b6c4d98e17f35bc2c7940a06100c2d448db7180b2d862164435c9c210e9860394e05aab23ff1b0203f662c588da5bc4411477a30b41136c488a0a63fcab5c15ac613226dae827a1d1fe95ece6b30bcee1560a8d408d264555e760f9bfc624c2c87fd0456c9bfb41bcd1a7b212786d2b67fd57804facfa1eef7cf2919d8b998c9789f763b4d9caa093a9ded43175d46a76b4d54f0ce0c5d2259b607e30a9d241263874fa59d6f570dc40d83a2d83bf9e9850d454c57806535a8998ae0357df92f00b9667344c24114ccc9ef539124b204e7e6e748c30a28a3d1d183997243eaccbbd33b0c1115a0327106a1e6a75271d4983086f632ff0eb8b4c63102cbcef5bb72dae1279d5de8eb19096d8cdb07fa8ea989788fac23e66e0488c193f3f3fea8c8838896bf3ae4b6848d42ced4bdf41abe6fc331b44225e7a1f7d3564147d5458e71aa909cb02be958bbc42e3aa5a27cc6eaf4b6fe51ae4495694d8ab6320aab920183fd5b31a359042fbd67391ec5e4d1892a2e5210141a494e9301b24a113c474c7f2a73457847
-ClientFinished = 140303000101170303003532d030e273773a8696c799981af6ced07f87482e81565e394e87c867f33df3d65b7506f1a626af91d4821d5f7a1f210ef8dd3c6d16
-Client_CloseNotify = 17030300130f629155382dba23c4e2c5f7f84e6f2ed3083d
-Server_CloseNotify = 1703030013b7257b0fecaf69d4f09e3f891e2a25d1e28845
+Server_MessageToSign = 20202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020202020544C5320312E332C2073657276657220436572746966696361746556657269667900F6F6F65E8DADCBFC7DFD3A7841469B0F9889BA468C9FF9DA0AE5CA4DEA8B50E4
+Server_MessageSignature = a2301a68dd1ceee6938fe9d40c46b9201b34d59952a37e06523a39cf8ba6c9c8b68ae94492af780516ed7b73c82812e99dd3fabea45e09d9c6848721c2808c61501b0c75e7fcaba5f78bef68a2c2b69b19558b3e40387eea93d25c7781c1cc00e9f519f7e2e4adb73e76d66089000a2dc866c2ed30bba50a0d457f19dc6eb9f3
+Record_ClientHello_1 = 16030100e0010000dc03034e640a3f2c2738f09c9418bd78edccd7559d0531199276d4d92a0e9ee9d77d0920a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef00061301130313020100008d0000000b0009000006736572766572ff01000100000a00140012001d00170018001901000101010201030104003300260024001d00208e7292cf3056dbb0d25fcbe55c107dc9bbf83dd9708f39203ba341249a7d9b63002b0003020304000d0020001e040305030603020308040805080604010501060102010402050206020202002d00020101001c00024001
+Record_ServerHello = 160303007a020000760303e5dd5948c435f7a38f0f0130708dc322d9df09abd4838117c183a7bb6d994f2c20a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef130100002e00330024001d00203e30f0f4ba551afd62768341175f5265e4daf0c8841617aa4fafdd2142320c22002b00020304
+Message_ServerHello = 020000760303e5dd5948c435f7a38f0f0130708dc322d9df09abd4838117c183a7bb6d994f2c20a80c165581a8e0d06c0018d54d3a06dd32cfd4051eb026fad3fd0ba99269e6ef130100002e00330024001d00203e30f0f4ba551afd62768341175f5265e4daf0c8841617aa4fafdd2142320c22002b00020304
+Record_ServerHandshakeMessages = 14030300010117030302a248de891d9c3624a67a6c6f0601ab7ac20c1f6a9e14d2e6007e999e130367a8af1bcfea9498fbce19df4505eece3a25da523cbe55ea1b3bda4e91995e455d500a4faa6227b7111e1c8547e2d7c179db215303d25827f3cd18f48f6491328cf5c0f814d388150bd9e9264aae491db6995069bea17665d5e0c817284d4ac21880054c3657331e23a9304dc88a15c04ec80bd3852bf7f9d3c6615b15fac83bbca031c6d2310d9f5d7a4b020a4f7c19062b65c05a1d3264b557ec9d8e0f7cee27e36f793039de8dd96edfca9009e0651034bff31d7f349eece01d99fcb5fcab840d7707c72299c3b5d04564e880a33c5e846c762e3d922bb55303d1d87cc0f06573f17dcb9b8ffd35bbd883c1cb3aa24fcc325005f768ce2fb624ca97b6c4d98e17f35bc2c7940a06100c2d448db7180b2d862164435c9c210e9860394e05aab23ff1b0203f662c588da5bc4411477a30b41136c488a0a63fcab5c15ac613226dae827a1d1fe95ece6b30bcee1560a8d408d264555e760f9bfc624c2c87fd0456c9bfb41bcd1a7b212786d2b67fd57804facfa1eef7cf2919d8b998c9789f763b4d9caa093a9ded43175d46a76b4d54f0ce0c5d2259b607e30a9d241263874fa59d6f570dc40d83a2d83bf9e9850d454c57806535a8998ae0357df92f00b9667344c24114ccc9ef539124b204e7e6e748c30a28a3d1d183997243eaccbbd33b0c1115a0327106a1e6a75271d4983086f632ff0eb8b4c63102cbcef5bb72dae1279d5de8eb19096d8cdb07fa8ea989788fac23e66e0488c193f3f3fea8c8838896bf3ae4b6848d42ced4bdf41abe6fc331b44225e7a1f7d3564147d5458e71aa909cb02be958bbc42e3aa5a27cc6eaf4b6fe51ae4495694d8ab6320aab920183fd5b31a359042fbd67391ec5e4d1892a2e5210141a494e9301b24a113c474c7f2a73457847
+Message_EncryptedExtensions = 080000240022000a00140012001d00170018001901000101010201030104001c0002400100000000
+Message_Server_Certificate = 0b0001b9000001b50001b0308201ac30820115a003020102020102300d06092a864886f70d01010b0500300e310c300a06035504031303727361301e170d3136303733303031323335395a170d3236303733303031323335395a300e310c300a0603550403130372736130819f300d06092a864886f70d010101050003818d0030818902818100b4bb498f8279303d980836399b36c6988c0c68de55e1bdb826d3901a2461eafd2de49a91d015abbc9a95137ace6c1af19eaa6af98c7ced43120998e187a80ee0ccb0524b1b018c3e0b63264d449a6d38e22a5fda430846748030530ef0461c8ca9d9efbfae8ea6d1d03e2bd193eff0ab9a8002c47428a6d35a8d88d79f7f1e3f0203010001a31a301830090603551d1304023000300b0603551d0f0404030205a0300d06092a864886f70d01010b05000381810085aad2a0e5b9276b908c65f73a7267170618a54c5f8a7b337d2df7a594365417f2eae8f8a58c8f8172f9319cf36b7fd6c55b80f21a03015156726096fd335e5e67f2dbf102702e608ccae6bec1fc63a42a99be5c3eb7107c3c54e9b9eb2bd5203b1c3b84e0a8b2f759409ba3eac9d91d402dcc0cc8f8961229ac9187b42b4de10000
+Message_Server_CertificateVerify = 0f00008408040080a2301a68dd1ceee6938fe9d40c46b9201b34d59952a37e06523a39cf8ba6c9c8b68ae94492af780516ed7b73c82812e99dd3fabea45e09d9c6848721c2808c61 50 1b0c75e7fcaba5f78bef68a2c2b69b19558b3e40387eea93d25c7781c1cc00e9f519f7e2e4adb73e76d66089000a2dc866c2ed30bba50a0d457f19dc6eb9f3
+Message_Server_Finished = 14000020c8c3a8f1bff5274061f4bc3a7caffbdc9616094ca625caa65f8e76ed46db74d3
+Record_ClientFinished = 140303000101170303003532d030e273773a8696c799981af6ced07f87482e81565e394e87c867f33df3d65b7506f1a626af91d4821d5f7a1f210ef8dd3c6d16
+Record_Client_CloseNotify = 17030300130f629155382dba23c4e2c5f7f84e6f2ed3083d
+Record_Server_CloseNotify = 1703030013b7257b0fecaf69d4f09e3f891e2a25d1e28845
diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp
index 891749d2b..db1eee91d 100644
--- a/src/tests/test_tls_cipher_state.cpp
+++ b/src/tests/test_tls_cipher_state.cpp
@@ -283,6 +283,16 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt1()
          result.confirm("expecting the correct MAC for server finished", cs->verify_peer_finished_mac(th_pre_server_finished,
                         expected_server_mac));
 
+         // advance Cipher_State with client_hello...server_Finished
+         // (allows receiving of application data, but does not yet allow such sending)
+         result.test_no_throw("state advancement is legal", [&]
+               {
+               cs->advance_with_server_finished(th_server_finished);
+               });
+
+         result.confirm("can read application data", cs->can_decrypt_application_traffic());
+         result.confirm("can not yet write application data", !cs->can_encrypt_application_traffic());
+
          // generate the MAC for the client Finished message
          const auto expected_client_mac = Botan::hex_decode("a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1 1a 03"
                                                             "9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61");
@@ -292,14 +302,15 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt1()
          // (under the client handshake traffic secret)
          encrypted_client_finished_message.encrypt(result, cs.get());
 
-         // advance Cipher_State with client_hello...server_Finished
-         // (allows sending of application data)
+         // advance Cipher_State with client_hello...client_Finished
+         // (allows generation of resumption PSKs)
          result.test_no_throw("state advancement is legal", [&]
                {
-               cs->advance_with_server_finished(th_server_finished);
+               cs->advance_with_client_finished(th_client_finished);
                });
 
          result.confirm("can write application data", cs->can_encrypt_application_traffic());
+         result.confirm("can read application data", cs->can_decrypt_application_traffic());
          result.confirm("can export key material", cs->can_export_keys());
          result.test_eq("key export produces expected result", cs->export_key(export_label, export_context, 16), expected_key_export);
 
@@ -314,13 +325,6 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt1()
          // (encrypted under the application traffic secret -- and a new sequence number)
          encrypted_application_data_server.decrypt(result, cs.get());
 
-         // advance Cipher_State with client_hello...client_Finished
-         // (allows generation of resumption PSKs)
-         result.test_no_throw("state advancement is legal", [&]
-               {
-               cs->advance_with_client_finished(th_client_finished);
-               });
-
          result.confirm("can export key material still", cs->can_export_keys());
          result.test_eq("key export result did not change", cs->export_key(export_label, export_context, 16), expected_key_export);
       }),
@@ -532,6 +536,19 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt0()
          result.confirm("expecting the correct MAC for server finished", cs->verify_peer_finished_mac(th_pre_server_finished,
                         expected_server_mac));
 
+         result.confirm("cannot read application data", !cs->can_decrypt_application_traffic());
+         result.confirm("cannot write application data", !cs->can_encrypt_application_traffic());
+
+         // advance Cipher_State with client_hello...server_Finished
+         // (allows receiving of application data, but no such sending)
+         result.test_no_throw("state advancement is legal", [&]
+               {
+               cs->advance_with_server_finished(th_server_finished);
+               });
+
+         result.confirm("can read application data", cs->can_decrypt_application_traffic());
+         result.confirm("cannot write application data", !cs->can_encrypt_application_traffic());
+
          // generate the MAC for the client Finished message
          const auto expected_client_mac = Botan::hex_decode("72 30 a9 c9 52 c2 5c d6 13 8f c5 e6 62 83 08 c4"
                                                             "1c 53 35 dd 81 b9 f9 6b ce a5 0f d3 2b da 41 6d");
@@ -544,14 +561,16 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt0()
 
       Botan_Tests::CHECK("application traffic after PSK (client side)", [&](Test::Result& result)
          {
-         // advance Cipher_State with client_hello...server_Finished
-         // (allows sending of application data)
+         // advance Cipher_State with client_hello...client_Finished
+         // (allows generation of resumption PSKs)
          result.test_no_throw("state advancement is legal", [&]
                {
-               cs->advance_with_server_finished(th_server_finished);
+               cs->advance_with_client_finished(th_client_finished);
                });
 
+         result.confirm("can read application data", cs->can_decrypt_application_traffic());
          result.confirm("can write application data", cs->can_encrypt_application_traffic());
+
          result.confirm("can export key material", cs->can_export_keys());
          result.test_eq("key export produces expected result", cs->export_key(export_label, export_context, 16), expected_key_export);
 
@@ -561,19 +580,6 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt0()
          // decrypt application data from server
          // (encrypted under the application traffic secret -- and a new sequence number)
          encrypted_application_data_server.decrypt(result, cs.get());
-         }),
-
-      Botan_Tests::CHECK("final state advancement", [&](Test::Result& result)
-         {
-         // advance Cipher_State with client_hello...client_Finished
-         // (allows generation of resumption PSKs)
-         result.test_no_throw("state advancement is legal", [&]
-               {
-               cs->advance_with_client_finished(th_client_finished);
-               });
-
-         result.confirm("can export key material still", cs->can_export_keys());
-         result.test_eq("key export result did not change", cs->export_key(export_label, export_context, 16), expected_key_export);
          })
       };
    }
diff --git a/src/tests/test_tls_handshake_layer_13.cpp b/src/tests/test_tls_handshake_layer_13.cpp
index 337fdbd94..54fbf6720 100644
--- a/src/tests/test_tls_handshake_layer_13.cpp
+++ b/src/tests/test_tls_handshake_layer_13.cpp
@@ -274,7 +274,7 @@ std::vector<Test::Result> prepare_message()
       {
       Botan_Tests::CHECK("prepare client hello", [&](auto& result)
          {
-         Client_Hello_13 hello({client_hello_message.cbegin()+4, client_hello_message.cend()});
+         auto hello = std::get<Client_Hello_13>(Client_Hello_13::parse({client_hello_message.cbegin()+4, client_hello_message.cend()}));
          Handshake_Layer hl(Connection_Side::CLIENT);
          Transcript_Hash_State th("SHA-256");
          result.test_eq("produces the same message", hl.prepare_message(hello, th), client_hello_message);
@@ -303,7 +303,7 @@ std::vector<Test::Result> full_client_handshake()
       {
       Botan_Tests::CHECK("client hello", [&](auto& result)
          {
-         Client_Hello_13 hello({client_hello_message.cbegin()+4, client_hello_message.cend()});
+         auto hello = std::get<Client_Hello_13>(Client_Hello_13::parse({client_hello_message.cbegin()+4, client_hello_message.cend()}));
          hl.prepare_message(hello, th);
          check_transcript_hash_empty(result, th);
          }),
@@ -374,8 +374,9 @@ std::vector<Test::Result> hello_retry_request_handshake()
       {
       Botan_Tests::CHECK("client hello 1", [&](auto& result)
          {
-         Client_Hello_13 hello({hrr_client_hello_msg.cbegin()+4, hrr_client_hello_msg.cend()});
-         hl.prepare_message(hello, th);
+         auto hello = std::get<Client_Hello_13>(Client_Hello_13::parse({hrr_client_hello_msg.cbegin()+4, hrr_client_hello_msg.cend()}));
+         auto msg = hl.prepare_message(hello, th);
+         result.test_eq("parsing and re-marshalling produces same message", msg, hrr_client_hello_msg);
          check_transcript_hash_empty(result, th);
          }),
 
diff --git a/src/tests/test_tls_handshake_state_13.cpp b/src/tests/test_tls_handshake_state_13.cpp
index fc1c275a9..235001f97 100644
--- a/src/tests/test_tls_handshake_state_13.cpp
+++ b/src/tests/test_tls_handshake_state_13.cpp
@@ -84,7 +84,9 @@ std::vector<Test::Result> handshake_message_filtering()
          {
          Client_Handshake_State_13 state;
 
-         auto filtered = state.sending(Client_Hello_13(client_hello_message));
+         auto client_hello = std::get<Client_Hello_13>(Client_Hello_13::parse(client_hello_message));
+
+         auto filtered = state.sending(std::move(client_hello));
          result.confirm("client can send client hello",
                         std::holds_alternative<std::reference_wrapper<Client_Hello_13>>(filtered));
 
@@ -93,7 +95,8 @@ std::vector<Test::Result> handshake_message_filtering()
          result.template test_throws<TLS_Exception>("client cannot receive client hello",
                "received an illegal handshake message", [&]
             {
-            state.received(Client_Hello_13(client_hello_message));
+            auto ch = std::get<Client_Hello_13>(Client_Hello_13::parse(client_hello_message));
+            state.received(std::move(ch));
             });
          }),
       Botan_Tests::CHECK("Client with server hello", [&](auto& result)
diff --git a/src/tests/test_tls_messages.cpp b/src/tests/test_tls_messages.cpp
index 17f569aaa..9ec3af073 100644
--- a/src/tests/test_tls_messages.cpp
+++ b/src/tests/test_tls_messages.cpp
@@ -1,6 +1,8 @@
 /*
 * (C) 2016 Juraj Somorovsky
 * (C) 2021 Elektrobit Automotive GmbH
+* (C) 2022 Hannes Rantzsch, René Meusel - neXenio GmbH
+* (C) 2022 René Meusel - Rohde & Schwarz Cybersecurity
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -464,18 +466,17 @@ class TLS_13_Message_Parsing_Test final : public Text_Based_Test
             {
             try
                {
-               const std::string extensions = vars.get_req_str("AdditionalData");
-
-               // TODO: When implementing a TLS 1.3 server we will likely switch to
-               //       a Client_Hello::parse() factory method that distinguishes
-               //       TLS 1.2 and TLS 1.3 client hellos while parsing and returns
-               //       a std::variant<> similarly to Server_Hello::parse().
-               //
-               // For now we perform a hard-coded distinction.
-               if(msg_type == "client_hello_13")
-                  {
-                  Botan::TLS::Client_Hello_13 ch(buffer);
+               std::visit([&](auto ch) {
+                  if constexpr(std::is_same_v<Botan::TLS::Client_Hello_12, decltype(ch)>)
+                     {
+                     result.confirm("expected Client_Hello_12", msg_type == "client_hello_12");
+                     }
+                  if constexpr(std::is_same_v<Botan::TLS::Client_Hello_13, decltype(ch)>)
+                     {
+                     result.confirm("expected Client_Hello_13", msg_type == "client_hello_13");
+                     }
 
+                  const std::string extensions = vars.get_req_str("AdditionalData");
                   std::vector<uint8_t> exts_buffer;
                   for(Botan::TLS::Handshake_Extension_Type const& type : ch.extensions().extension_types())
                      {
@@ -492,9 +493,9 @@ class TLS_13_Message_Parsing_Test final : public Text_Based_Test
                      ciphersuites_buffer.push_back(Botan::get_byte<1>(cs));
                      }
                   result.test_eq("Supported ciphersuites", ciphersuites_buffer, ciphersuite);
-                  }
 
                   result.confirm("this is a positive test that should not have failed yet", is_positive_test);
+                  }, Botan::TLS::Client_Hello_13::parse(buffer));
                }
             catch (const std::exception &ex)
                {
diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp
index 6b806cdd8..8c36c2c7c 100644
--- a/src/tests/test_tls_rfc8448.cpp
+++ b/src/tests/test_tls_rfc8448.cpp
@@ -1,6 +1,7 @@
 /*
 * (C) 2021 Jack Lloyd
-* (C) 2021 René Meusel
+*     2021, 2022 René Meusel, Hannes Rantzsch - neXenio GmbH
+*     2022       René Meusel - Rohde & Schwarz Cybersecurity GmbH
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -61,12 +62,11 @@ void add_entropy(Botan_Tests::Fixed_Output_RNG& rng, const std::vector<uint8_t>&
    rng.add_entropy(bin.data(), bin.size());
    }
 
-// TODO: use this once the server side is being implemented
-// std::unique_ptr<Botan::Private_Key> server_private_key()
-//    {
-//    Botan::DataSource_Memory in(Test::read_data_file("tls_13_rfc8448/server_key.pem"));
-//    return Botan::PKCS8::load_key(in);
-//    }
+std::unique_ptr<Botan::Private_Key> server_private_key()
+   {
+   Botan::DataSource_Memory in(Test::read_data_file("tls_13_rfc8448/server_key.pem"));
+   return Botan::PKCS8::load_key(in);
+   }
 
 Botan::X509_Certificate server_certificate()
    {
@@ -132,7 +132,18 @@ std::chrono::system_clock::time_point from_milliseconds_since_epoch(uint64_t mse
    }
 
 using Modify_Exts_Fn = std::function<void(Botan::TLS::Extensions&, Botan::TLS::Connection_Side, Botan::TLS::Handshake_Type)>;
-using MockSignature_Fn = std::function<std::vector<uint8_t>(const std::vector<uint8_t>&,const std::string&,Signature_Format)>;
+
+/**
+ * We cannot actually reproduce the signatures stated in RFC 8448 as their
+ * signature scheme is probabilistic and we're lacking the correct RNG
+ * input. Hence, signatures are know beforehand and just reproduced by the
+ * TLS callback when requested.
+ */
+struct MockSignature
+   {
+   std::vector<uint8_t> message_to_sign;
+   std::vector<uint8_t> signature_to_produce;
+   };
 
 /**
  * Subclass of the Botan::TLS::Callbacks instrumenting all available callbacks.
@@ -145,11 +156,11 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks
    public:
       Test_TLS_13_Callbacks(
          Modify_Exts_Fn modify_exts_cb,
-         MockSignature_Fn mock_signature_fn,
+         std::vector<MockSignature> mock_signatures,
          uint64_t timestamp) :
          session_activated_called(false),
          m_modify_exts(std::move(modify_exts_cb)),
-         m_mock_signature(std::move(mock_signature_fn)),
+         m_mock_signatures(std::move(mock_signatures)),
          m_timestamp(from_milliseconds_since_epoch(timestamp))
          {}
 
@@ -234,12 +245,29 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks
          Signature_Format format,
          const std::vector<uint8_t>& msg) override
          {
+         BOTAN_UNUSED(key, rng);
          count_callback_invocation("tls_sign_message");
 
-         auto mock_signature = m_mock_signature(msg, emsa, format);
-         return (mock_signature.empty())
-            ? Callbacks::tls_sign_message(key, rng, emsa, format, msg)
-            : mock_signature;
+         if(format != Signature_Format::Standard)
+            {
+            throw Test_Error("TLS implementation selected unexpected signature format");
+            }
+
+         if(emsa != "PSSR(SHA-256,MGF1,32)")
+            {
+            throw Test_Error("TLS implementation selected unexpected padding " + emsa);
+            }
+
+         for(const auto& mock : m_mock_signatures)
+            {
+            if(mock.message_to_sign == msg)
+               {
+               return mock.signature_to_produce;
+               }
+            }
+
+         throw Test_Error("TLS implementation produced an unexpected message to be signed: " +
+                          Botan::hex_encode(msg));
          }
 
       bool tls_verify_message(
@@ -278,6 +306,20 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks
       void tls_inspect_handshake_msg(const Handshake_Message& message) override
          {
          count_callback_invocation("tls_inspect_handshake_msg_" + message.type_string());
+
+         try {
+            auto serialized_message = message.serialize();
+
+            serialized_messages
+               .try_emplace(message.type_string())
+               .first->second
+               .emplace_back(std::move(serialized_message));
+         } catch (const Not_Implemented&) {
+            // TODO: Once the server implementation is finished, this crutch
+            //       can likely be removed, as all message types will have a
+            //       serialization method with actual business logic. :o)
+         }
+
          return Callbacks::tls_inspect_handshake_msg(message);
          }
 
@@ -353,44 +395,56 @@ class Test_TLS_13_Callbacks : public Botan::TLS::Callbacks
       bool session_activated_called;
 
       std::vector<Botan::X509_Certificate> certificate_chain;
+      std::map<std::string, std::vector<std::vector<uint8_t>>> serialized_messages;
 
    private:
       std::vector<uint8_t>                  send_buffer;
       std::vector<uint8_t>                  receive_buffer;
       uint64_t                              received_seq_no;
       Modify_Exts_Fn                        m_modify_exts;
-      MockSignature_Fn     m_mock_signature;
+      std::vector<MockSignature>            m_mock_signatures;
       std::chrono::system_clock::time_point m_timestamp;
 
       mutable std::map<std::string, unsigned int> m_callback_invocations;
    };
 
-class Test_Client_Credentials : public Botan::Credentials_Manager
+class Test_Credentials : public Botan::Credentials_Manager
    {
    public:
-      Test_Client_Credentials() : m_bogus_key(client_private_key()) {}
+      Test_Credentials()
+         : m_bogus_client_key(client_private_key())
+         , m_server_key(server_private_key()) {}
 
       std::vector<Botan::X509_Certificate> cert_chain(
          const std::vector<std::string>& cert_key_types,
          const std::string& type,
          const std::string& context) override
          {
-         BOTAN_UNUSED(cert_key_types, type, context);
-         return { client_certificate() };
+         BOTAN_UNUSED(cert_key_types, context);
+         return
+            {
+            (type == "tls-client")
+            ? client_certificate()
+            : server_certificate()
+            };
          }
 
       Botan::Private_Key* private_key_for(const Botan::X509_Certificate& cert,
                                           const std::string& type,
                                           const std::string& context) override
          {
-         BOTAN_UNUSED(cert, type, context);
+         BOTAN_UNUSED(cert, context);
+
+         return (type == "tls-client")
          // Note that this key is just a stand-in and not the actual private
          // key for the client certificate. RFC 8448 does not reveal this key.
-         return m_bogus_key.get();
+            ? m_bogus_client_key.get()
+            : m_server_key.get();
          }
 
    private:
-      std::unique_ptr<Botan::Private_Key> m_bogus_key;
+      std::unique_ptr<Botan::Private_Key> m_bogus_client_key;
+      std::unique_ptr<Botan::Private_Key> m_server_key;
    };
 
 class RFC8448_Text_Policy : public Botan::TLS::Text_Policy
@@ -524,10 +578,10 @@ class TLS_Context
       TLS_Context(std::unique_ptr<Botan::RandomNumberGenerator> rng_in,
                   RFC8448_Text_Policy policy,
                   Modify_Exts_Fn modify_exts_cb,
-                  MockSignature_Fn mock_signature_cb,
+                  std::vector<MockSignature> mock_signatures,
                   uint64_t timestamp,
                   std::optional<std::vector<uint8_t>> session)
-         : m_callbacks(std::move(modify_exts_cb), std::move(mock_signature_cb), timestamp)
+         : m_callbacks(std::move(modify_exts_cb), std::move(mock_signatures), timestamp)
          , m_rng(std::move(rng_in))
          , m_policy(std::move(policy))
          {
@@ -593,11 +647,19 @@ class TLS_Context
          return m_callbacks.certificate_chain;
          }
 
+      decltype(auto) observed_handshake_messages() const
+         {
+         return m_callbacks.serialized_messages;
+         }
+
+      /**
+       * Send application data through the secure channel
+       */
       virtual void send(const std::vector<uint8_t>& data) = 0;
 
    protected:
-      Test_TLS_13_Callbacks   m_callbacks;
-      Test_Client_Credentials m_creds;
+      Test_TLS_13_Callbacks m_callbacks;
+      Test_Credentials      m_creds;
 
       std::unique_ptr<Botan::RandomNumberGenerator> m_rng;
       RFC8448_Session_Manager                       m_session_mgr;
@@ -612,8 +674,8 @@ class Client_Context : public TLS_Context
                      uint64_t timestamp,
                      Modify_Exts_Fn modify_exts_cb,
                      std::optional<std::vector<uint8_t>> session = std::nullopt,
-                     MockSignature_Fn mock_signature_cb = [](auto,auto,auto) { return std::vector<uint8_t>(); })
-         : TLS_Context(std::move(rng_in), std::move(policy), std::move(modify_exts_cb), std::move(mock_signature_cb), timestamp, std::move(session))
+                     std::vector<MockSignature> mock_signatures = {})
+         : TLS_Context(std::move(rng_in), std::move(policy), std::move(modify_exts_cb), std::move(mock_signatures), timestamp, std::move(session))
          , client(m_callbacks, m_session_mgr, m_creds, m_policy, *m_rng,
                   Botan::TLS::Server_Information("server"),
                   Botan::TLS::Protocol_Version::TLS_V13)
@@ -627,11 +689,34 @@ class Client_Context : public TLS_Context
       Botan::TLS::Client client;
    };
 
+class Server_Context : public TLS_Context
+   {
+   public:
+      Server_Context(std::unique_ptr<Botan::RandomNumberGenerator> rng,
+                     RFC8448_Text_Policy policy,
+                     uint64_t timestamp,
+                     Modify_Exts_Fn modify_exts_cb,
+                     std::vector<MockSignature> mock_signatures)
+         : TLS_Context(std::move(rng), std::move(policy),
+            std::move(modify_exts_cb),
+            std::move(mock_signatures),
+            timestamp,
+            std::nullopt /* session not yet needed */)
+         , server(m_callbacks, m_session_mgr, m_creds, m_policy, *m_rng, false /* DTLS NYI */) {}
+
+      void send(const std::vector<uint8_t>& data) override
+         {
+         server.send(data.data(), data.size());
+         }
+
+      Botan::TLS::Server server;
+   };
+
 /**
  * Because of the nature of the RFC 8448 test data we need to produce bit-compatible
  * TLS messages. Hence we sort the generated TLS extensions exactly as expected.
  */
-void sort_extensions(Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side)
+void sort_client_extensions(Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side)
    {
    if(side == Botan::TLS::Connection_Side::CLIENT)
       {
@@ -662,6 +747,35 @@ void sort_extensions(Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side s
       }
    }
 
+/**
+ * Because of the nature of the RFC 8448 test data we need to produce bit-compatible
+ * TLS messages. Hence we sort the generated TLS extensions exactly as expected.
+ */
+[[maybe_unused]] void sort_server_extensions(Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side)
+   {
+   if(side == Botan::TLS::Connection_Side::SERVER)
+      {
+      const std::vector<Botan::TLS::Handshake_Extension_Type> expected_order =
+         {
+         Botan::TLS::Handshake_Extension_Type::TLSEXT_SUPPORTED_GROUPS,
+         Botan::TLS::Handshake_Extension_Type::TLSEXT_KEY_SHARE,
+         Botan::TLS::Handshake_Extension_Type::TLSEXT_SUPPORTED_VERSIONS,
+         Botan::TLS::Handshake_Extension_Type::TLSEXT_SIGNATURE_ALGORITHMS,
+         Botan::TLS::Handshake_Extension_Type::TLSEXT_RECORD_SIZE_LIMIT,
+         Botan::TLS::Handshake_Extension_Type::TLSEXT_SERVER_NAME_INDICATION
+         };
+
+      for(const auto ext_type : expected_order)
+         {
+         auto ext = exts.take(ext_type);
+         if(ext != nullptr)
+            {
+            exts.add(std::move(ext));
+            }
+         }
+      }
+   }
+
 void add_renegotiation_extension(Botan::TLS::Extensions& exts)
    {
    // Renegotiation is not possible in TLS 1.3. Nevertheless, RFC 8448 requires
@@ -674,6 +788,28 @@ void add_early_data_indication(Botan::TLS::Extensions& exts)
    exts.add(new Botan::TLS::EarlyDataIndication());
    }
 
+[[maybe_unused]] std::vector<uint8_t> strip_message_header(const std::vector<uint8_t>& msg)
+   {
+   BOTAN_ASSERT_NOMSG(msg.size() >= 4);
+   return {msg.begin() + 4, msg.end()};
+   }
+
+std::vector<MockSignature> make_mock_signatures(const VarMap& vars)
+   {
+   std::vector<MockSignature> result;
+
+   auto mock = [&](const std::string& msg, const std::string& sig)
+      {
+      if(vars.has_key(msg) && vars.has_key(sig))
+         result.push_back({vars.get_opt_bin(msg), vars.get_opt_bin(sig)});
+      };
+
+   mock("Server_MessageToSign", "Server_MessageSignature");
+   mock("Client_MessageToSign", "Client_MessageSignature");
+
+   return result;
+   }
+
 }  // namespace
 
 /**
@@ -698,38 +834,87 @@ void add_early_data_indication(Botan::TLS::Extensions& exts)
  * tls-policy/rfc8448_*.txt
  *   Each RFC 8448 section test required a slightly adapted Botan TLS policy
  *   to enable/disable certain features under test.
+ *
+ * While the test cases are split into Client-side and Server-side tests, the
+ * transcript data is reused. See the concrete implementations of the abstract
+ * Test_TLS_RFC8448 test class.
  */
-class Test_TLS_RFC8448 final : public Text_Based_Test
+class Test_TLS_RFC8448 : public Text_Based_Test
    {
+   protected:
+      virtual std::vector<Test::Result> simple_1_rtt(const VarMap& vars) = 0;
+      virtual std::vector<Test::Result> resumed_handshake_with_0_rtt(const VarMap& vars) = 0;
+      virtual std::vector<Test::Result> hello_retry_request(const VarMap& vars) = 0;
+      virtual std::vector<Test::Result> client_authentication(const VarMap& vars) = 0;
+      virtual std::vector<Test::Result> middlebox_compatibility(const VarMap& vars) = 0;
+
+      virtual std::string side() const = 0;
+
    public:
       Test_TLS_RFC8448()
-         : Text_Based_Test("tls_13_rfc8448/transcripts.vec", "RNG_Pool,CurrentTimestamp,ClientHello_1,ServerHello,ServerHandshakeMessages,ClientFinished,Client_CloseNotify,Server_CloseNotify", "HelloRetryRequest,ClientHello_2,NewSessionTicket,Client_AppData,Client_AppData_Record,Server_AppData,Server_AppData_Record,Client_EarlyAppData,Client_EarlyAppData_Record,SessionTicket,MessageToSign,MessageSignature") {}
-
-   Test::Result run_one_test(const std::string& header,
-                             const VarMap& vars) override
-      {
-      if(header == "Simple_1RTT_Handshake")
-         return Test::Result("Simple 1-RTT (Client side)", simple_1_rtt_client_hello(vars));
-      else if(header == "Resumed_0RTT_Handshake")
-         return Test::Result("Resumption with 0-RTT data", resumed_handshake_with_0_rtt(vars));
-      else if(header == "HelloRetryRequest_Handshake")
-         return Test::Result("Handshake involving Hello Retry Request (Client side)", hello_retry_request(vars));
-      else if(header == "Client_Authentication_Handshake")
-         return Test::Result("Client Authentication (Client side)", client_authentication(vars));
-      else if(header == "Middlebox_Compatibility_Mode")
-         return Test::Result("Middlebox Compatibility Mode (Client side)", middlebox_compatibility(vars));
-      else
-         return Test::Result::Failure("test dispatcher", "unknown sub-test: " + header);
-      }
+         : Text_Based_Test("tls_13_rfc8448/transcripts.vec",
+                           // mandatory data fields
+                           "Client_RNG_Pool,"
+                           "Server_RNG_Pool,"
+                           "CurrentTimestamp,"
+                           "Record_ClientHello_1,"
+                           "Record_ServerHello,"
+                           "Record_ServerHandshakeMessages,"
+                           "Record_ClientFinished,"
+                           "Record_Client_CloseNotify,"
+                           "Record_Server_CloseNotify",
+                           // optional data fields
+                           "Message_ServerHello,"
+                           "Message_EncryptedExtensions,"
+                           "Message_Server_Certificate,"
+                           "Message_Server_CertificateVerify,"
+                           "Message_Server_Finished,"
+                           "Record_HelloRetryRequest,"
+                           "Record_ClientHello_2,"
+                           "Record_NewSessionTicket,"
+                           "Client_AppData,"
+                           "Record_Client_AppData,"
+                           "Server_AppData,"
+                           "Record_Server_AppData,"
+                           "Client_EarlyAppData,"
+                           "Record_Client_EarlyAppData,"
+                           "SessionTicket,"
+                           "Server_MessageToSign,"
+                           "Server_MessageSignature,"
+                           "Client_MessageToSign,"
+                           "Client_MessageSignature,"
+                           "HelloRetryRequest_Cookie") {}
+
+      Test::Result run_one_test(const std::string& header,
+                                const VarMap& vars) override
+         {
+         if(header == "Simple_1RTT_Handshake")
+            return Test::Result("Simple 1-RTT (" + side() + " side)", simple_1_rtt(vars));
+         else if(header == "Resumed_0RTT_Handshake")
+            return Test::Result("Resumption with 0-RTT data (" + side() + " side)", resumed_handshake_with_0_rtt(vars));
+         else if(header == "HelloRetryRequest_Handshake")
+            return Test::Result("Handshake involving Hello Retry Request (" + side() + " side)", hello_retry_request(vars));
+         else if(header == "Client_Authentication_Handshake")
+            return Test::Result("Client Authentication (" + side() + " side)", client_authentication(vars));
+         else if(header == "Middlebox_Compatibility_Mode")
+            return Test::Result("Middlebox Compatibility Mode (" + side() + " side)", middlebox_compatibility(vars));
+         else
+            return Test::Result::Failure("test dispatcher", "unknown sub-test: " + header);
+         }
+   };
 
+class Test_TLS_RFC8448_Client : public Test_TLS_RFC8448
+   {
    private:
-      static std::vector<Test::Result> simple_1_rtt_client_hello(const VarMap& vars)
+      std::string side() const override { return "Client"; }
+
+      std::vector<Test::Result> simple_1_rtt(const VarMap& vars) override
          {
          auto rng = std::make_unique<Botan_Tests::Fixed_Output_RNG>("");
 
          // 32 - for client hello random
          // 32 - for KeyShare (eph. x25519 key pair)
-         add_entropy(*rng, vars.get_req_bin("RNG_Pool"));
+         add_entropy(*rng, vars.get_req_bin("Client_RNG_Pool"));
 
          auto add_extensions_and_sort = [](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side, Botan::TLS::Handshake_Type which_message)
             {
@@ -741,7 +926,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                exts.add(new Botan::TLS::Session_Ticket());
 
                add_renegotiation_extension(exts);
-               sort_extensions(exts, side);
+               sort_client_extensions(exts, side);
                }
             };
 
@@ -760,13 +945,13 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                   "tls_modify_extensions_client_hello"
                   });
 
-               result.test_eq("TLS client hello", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1"));
+               result.test_eq("TLS client hello", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientHello_1"));
                }),
 
             Botan_Tests::CHECK("Server Hello", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
-               const auto server_hello = vars.get_req_bin("ServerHello");
+               const auto server_hello = vars.get_req_bin("Record_ServerHello");
                // splitting the input data to test partial reads
                const std::vector<uint8_t> server_hello_a(server_hello.begin(), server_hello.begin() + 20);
                const std::vector<uint8_t> server_hello_b(server_hello.begin() + 20, server_hello.end());
@@ -787,7 +972,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
             Botan_Tests::CHECK("Server HS messages .. Client Finished", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
-               ctx->client.received_data(vars.get_req_bin("ServerHandshakeMessages"));
+               ctx->client.received_data(vars.get_req_bin("Record_ServerHandshakeMessages"));
 
                ctx->check_callback_invocations(result, "encrypted handshake messages received",
                   {
@@ -806,14 +991,14 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                result.require("client is active", ctx->client.is_active());
 
                result.test_eq("correct handshake finished", ctx->pull_send_buffer(),
-                              vars.get_req_bin("ClientFinished"));
+                              vars.get_req_bin("Record_ClientFinished"));
                }),
 
             Botan_Tests::CHECK("Post-Handshake: NewSessionTicket", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
                result.require("no sessions so far", ctx->stored_sessions().empty());
-               ctx->client.received_data(vars.get_req_bin("NewSessionTicket"));
+               ctx->client.received_data(vars.get_req_bin("Record_NewSessionTicket"));
 
                ctx->check_callback_invocations(result, "new session ticket received",
                   {
@@ -835,13 +1020,13 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                ctx->check_callback_invocations(result, "application data sent", { "tls_emit_data" });
 
                result.test_eq("correct client application data", ctx->pull_send_buffer(),
-                              vars.get_req_bin("Client_AppData_Record"));
+                              vars.get_req_bin("Record_Client_AppData"));
                }),
 
             Botan_Tests::CHECK("Receive Application Data", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
-               ctx->client.received_data(vars.get_req_bin("Server_AppData_Record"));
+               ctx->client.received_data(vars.get_req_bin("Record_Server_AppData"));
 
                ctx->check_callback_invocations(result, "application data sent", { "tls_record_received" });
 
@@ -855,10 +1040,10 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                result.require("ctx is available", ctx != nullptr);
                ctx->client.close();
 
-               result.test_eq("close payload", ctx->pull_send_buffer(), vars.get_req_bin("Client_CloseNotify"));
+               result.test_eq("close payload", ctx->pull_send_buffer(), vars.get_req_bin("Record_Client_CloseNotify"));
                ctx->check_callback_invocations(result, "CLOSE_NOTIFY sent", { "tls_emit_data" });
 
-               ctx->client.received_data(vars.get_req_bin("Server_CloseNotify"));
+               ctx->client.received_data(vars.get_req_bin("Record_Server_CloseNotify"));
                ctx->check_callback_invocations(result, "CLOSE_NOTIFY received", { "tls_alert", "tls_peer_closed_connection" });
 
                result.confirm("connection is closed", ctx->client.is_closed());
@@ -866,13 +1051,13 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
             };
          }
 
-      static std::vector<Test::Result> resumed_handshake_with_0_rtt(const VarMap& vars)
+      std::vector<Test::Result> resumed_handshake_with_0_rtt(const VarMap& vars) override
          {
          auto rng = std::make_unique<Botan_Tests::Fixed_Output_RNG>("");
 
          // 32 - for client hello random
          // 32 - for KeyShare (eph. x25519 key pair)
-         add_entropy(*rng, vars.get_req_bin("RNG_Pool"));
+         add_entropy(*rng, vars.get_req_bin("Client_RNG_Pool"));
 
          auto add_extensions_and_sort = [](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side, Botan::TLS::Handshake_Type which_message)
             {
@@ -887,7 +1072,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                // Currently, the production implementation will never add this
                // extension even if the resumed session would allow early data.
                add_early_data_indication(exts);
-               sort_extensions(exts, side);
+               sort_client_extensions(exts, side);
                }
             };
 
@@ -911,7 +1096,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                   "tls_current_timestamp"
                   });
 
-               result.test_eq("TLS client hello", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1"));
+               result.test_eq("TLS client hello", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientHello_1"));
                })
 
             // TODO: The rest of this test vector requires 0-RTT which is not
@@ -920,7 +1105,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
             };
          }
 
-      static std::vector<Test::Result> hello_retry_request(const VarMap& vars)
+      std::vector<Test::Result> hello_retry_request(const VarMap& vars) override
          {
          auto add_extensions_and_sort = [flights = 0](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side, Botan::TLS::Handshake_Type which_message) mutable
             {
@@ -940,7 +1125,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                   exts.add(new Padding(175));
                   }
 
-               sort_extensions(exts, side);
+               sort_client_extensions(exts, side);
                }
             };
 
@@ -951,7 +1136,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
          // 32 - client hello random
          // 32 - eph. x25519 key pair
          // 32 - eph. P-256 key pair
-         add_entropy(*rng, vars.get_req_bin("RNG_Pool"));
+         add_entropy(*rng, vars.get_req_bin("Client_RNG_Pool"));
 
          std::unique_ptr<Client_Context> ctx;
 
@@ -968,13 +1153,13 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                   "tls_modify_extensions_client_hello",
                   });
 
-               result.test_eq("TLS client hello (1)", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1"));
+               result.test_eq("TLS client hello (1)", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientHello_1"));
                }),
 
             Botan_Tests::CHECK("Hello Retry Request .. second Client Hello", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
-               ctx->client.received_data(vars.get_req_bin("HelloRetryRequest"));
+               ctx->client.received_data(vars.get_req_bin("Record_HelloRetryRequest"));
 
                ctx->check_callback_invocations(result, "hello retry request received",
                   {
@@ -986,13 +1171,13 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                   "tls_decode_group_param"
                   });
 
-               result.test_eq("TLS client hello (2)", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_2"));
+               result.test_eq("TLS client hello (2)", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientHello_2"));
                }),
 
             Botan_Tests::CHECK("Server Hello", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
-               ctx->client.received_data(vars.get_req_bin("ServerHello"));
+               ctx->client.received_data(vars.get_req_bin("Record_ServerHello"));
 
                ctx->check_callback_invocations(result, "server hello received",
                   {
@@ -1005,7 +1190,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
             Botan_Tests::CHECK("Server HS Messages .. Client Finished", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
-               ctx->client.received_data(vars.get_req_bin("ServerHandshakeMessages"));
+               ctx->client.received_data(vars.get_req_bin("Record_ServerHandshakeMessages"));
 
                ctx->check_callback_invocations(result, "encrypted handshake messages received",
                   {
@@ -1021,7 +1206,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                   "tls_verify_message"
                   });
 
-               result.test_eq("client finished", ctx->pull_send_buffer(), vars.get_req_bin("ClientFinished"));
+               result.test_eq("client finished", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientFinished"));
                }),
 
             Botan_Tests::CHECK("Close Connection", [&](Test::Result& result)
@@ -1029,9 +1214,9 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                result.require("ctx is available", ctx != nullptr);
                ctx->client.close();
                ctx->check_callback_invocations(result, "encrypted handshake messages received", { "tls_emit_data" });
-               result.test_eq("client close notify", ctx->pull_send_buffer(), vars.get_req_bin("Client_CloseNotify"));
+               result.test_eq("client close notify", ctx->pull_send_buffer(), vars.get_req_bin("Record_Client_CloseNotify"));
 
-               ctx->client.received_data(vars.get_req_bin("Server_CloseNotify"));
+               ctx->client.received_data(vars.get_req_bin("Record_Server_CloseNotify"));
                ctx->check_callback_invocations(result, "encrypted handshake messages received", { "tls_alert", "tls_peer_closed_connection" });
 
                result.confirm("connection is closed", ctx->client.is_closed());
@@ -1039,53 +1224,30 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
             };
          }
 
-      static std::vector<Test::Result> client_authentication(const VarMap& vars)
+      std::vector<Test::Result> client_authentication(const VarMap& vars) override
          {
          auto rng = std::make_unique<Botan_Tests::Fixed_Output_RNG>("");
 
          // 32 - for client hello random
          // 32 - for eph. x25519 key pair
-         add_entropy(*rng, vars.get_req_bin("RNG_Pool"));
+         add_entropy(*rng, vars.get_req_bin("Client_RNG_Pool"));
 
          auto add_extensions_and_sort = [&](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side, Botan::TLS::Handshake_Type which_message)
             {
             if(which_message == Handshake_Type::CLIENT_HELLO)
                {
                add_renegotiation_extension(exts);
-               sort_extensions(exts, side);
+               sort_client_extensions(exts, side);
                }
             };
 
-         auto sign_certificate_verify = [&](const std::vector<uint8_t>& msg,
-                                            const std::string& emsa,
-                                            Signature_Format format)
-            {
-            BOTAN_UNUSED(emsa, format);
-
-            if(msg != vars.get_req_bin("MessageToSign"))
-               {
-               throw Test_Error("Unexpected message to be signed: " + Botan::hex_encode(msg));
-               }
-
-            if(format != Signature_Format::Standard)
-               {
-               throw Test_Error("TLS implementation selected unexpected signature format");
-               }
-
-            if(emsa != "PSSR(SHA-256,MGF1,32)")
-               {
-               throw Test_Error("TLS implementation selected unexpected padding " + emsa);
-               }
-
-            return vars.get_req_bin("MessageSignature");
-            };
-
          std::unique_ptr<Client_Context> ctx;
 
          return {
             Botan_Tests::CHECK("Client Hello", [&](Test::Result& result)
                {
-               ctx = std::make_unique<Client_Context>(std::move(rng), read_tls_policy("rfc8448_1rtt"), vars.get_req_u64("CurrentTimestamp"), add_extensions_and_sort, std::nullopt, sign_certificate_verify);
+
+               ctx = std::make_unique<Client_Context>(std::move(rng), read_tls_policy("rfc8448_1rtt"), vars.get_req_u64("CurrentTimestamp"), add_extensions_and_sort, std::nullopt, make_mock_signatures(vars));
 
                ctx->check_callback_invocations(result, "initial callbacks", {
                   "tls_emit_data",
@@ -1093,12 +1255,12 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                   "tls_modify_extensions_client_hello",
                   });
 
-               result.test_eq("Client Hello", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1"));
+               result.test_eq("Client Hello", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientHello_1"));
                }),
 
             Botan_Tests::CHECK("Server Hello", [&](auto& result)
                {
-               ctx->client.received_data(vars.get_req_bin("ServerHello"));
+               ctx->client.received_data(vars.get_req_bin("Record_ServerHello"));
 
                ctx->check_callback_invocations(result, "callbacks after server hello", {
                   "tls_examine_extensions_server_hello",
@@ -1108,7 +1270,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
 
             Botan_Tests::CHECK("other handshake messages and client auth", [&](Test::Result& result)
                {
-               ctx->client.received_data(vars.get_req_bin("ServerHandshakeMessages"));
+               ctx->client.received_data(vars.get_req_bin("Record_ServerHandshakeMessages"));
 
                ctx->check_callback_invocations(result, "signing callbacks invoked", {
                   "tls_sign_message",
@@ -1129,19 +1291,19 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
 
                // ClientFinished contains the entire coalesced client authentication flight
                // Messages: Certificate, CertificateVerify, Finished
-               result.test_eq("Client Auth and Finished", ctx->pull_send_buffer(), vars.get_req_bin("ClientFinished"));
+               result.test_eq("Client Auth and Finished", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientFinished"));
                }),
 
             Botan_Tests::CHECK("Close Connection", [&](Test::Result& result)
                {
                ctx->client.close();
-               result.test_eq("Client close_notify", ctx->pull_send_buffer(), vars.get_req_bin("Client_CloseNotify"));
+               result.test_eq("Client close_notify", ctx->pull_send_buffer(), vars.get_req_bin("Record_Client_CloseNotify"));
 
                ctx->check_callback_invocations(result, "after sending close notify", {
                   "tls_emit_data",
                   });
 
-               ctx->client.received_data(vars.get_req_bin("Server_CloseNotify"));
+               ctx->client.received_data(vars.get_req_bin("Record_Server_CloseNotify"));
                result.confirm("connection closed", ctx->client.is_closed());
 
                ctx->check_callback_invocations(result, "after receiving close notify", {
@@ -1152,21 +1314,21 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
          };
       }
 
-      static std::vector<Test::Result> middlebox_compatibility(const VarMap& vars)
+      std::vector<Test::Result> middlebox_compatibility(const VarMap& vars) override
          {
          auto rng = std::make_unique<Botan_Tests::Fixed_Output_RNG>("");
 
          // 32 - client hello random
          // 32 - legacy session ID
          // 32 - eph. x25519 key pair
-         add_entropy(*rng, vars.get_req_bin("RNG_Pool"));
+         add_entropy(*rng, vars.get_req_bin("Client_RNG_Pool"));
 
          auto add_extensions_and_sort = [&](Botan::TLS::Extensions& exts, Botan::TLS::Connection_Side side, Botan::TLS::Handshake_Type which_message)
             {
             if(which_message == Handshake_Type::CLIENT_HELLO)
                {
                add_renegotiation_extension(exts);
-               sort_extensions(exts, side);
+               sort_client_extensions(exts, side);
                }
             };
 
@@ -1177,7 +1339,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                {
                ctx = std::make_unique<Client_Context>(std::move(rng), read_tls_policy("rfc8448_compat"), vars.get_req_u64("CurrentTimestamp"), add_extensions_and_sort);
 
-               result.test_eq("Client Hello", ctx->pull_send_buffer(), vars.get_req_bin("ClientHello_1"));
+               result.test_eq("Client Hello", ctx->pull_send_buffer(), vars.get_req_bin("Record_ClientHello_1"));
 
                ctx->check_callback_invocations(result, "client hello prepared",
                   {
@@ -1190,9 +1352,9 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
             Botan_Tests::CHECK("Server Hello + other handshake messages", [&](Test::Result& result)
                {
                result.require("ctx is available", ctx != nullptr);
-               ctx->client.received_data(Botan::concat(vars.get_req_bin("ServerHello"),
+               ctx->client.received_data(Botan::concat(vars.get_req_bin("Record_ServerHello"),
                                                       // ServerHandshakeMessages contains the expected ChangeCipherSpec record
-                                                      vars.get_req_bin("ServerHandshakeMessages")));
+                                                      vars.get_req_bin("Record_ServerHandshakeMessages")));
 
                ctx->check_callback_invocations(result, "callbacks after server's first flight", {
                   "tls_inspect_handshake_msg_server_hello",
@@ -1211,7 +1373,7 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
 
                result.test_eq("CCS + Client Finished", ctx->pull_send_buffer(),
                               // ClientFinished contains the expected ChangeCipherSpec record
-                              vars.get_req_bin("ClientFinished"));
+                              vars.get_req_bin("Record_ClientFinished"));
 
                result.confirm("client is ready to send application traffic", ctx->client.is_active());
                }),
@@ -1221,12 +1383,12 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
                result.require("ctx is available", ctx != nullptr);
                ctx->client.close();
 
-               result.test_eq("Client close_notify", ctx->pull_send_buffer(), vars.get_req_bin("Client_CloseNotify"));
+               result.test_eq("Client close_notify", ctx->pull_send_buffer(), vars.get_req_bin("Record_Client_CloseNotify"));
 
                result.require("client cannot send application traffic anymore", !ctx->client.is_active());
                result.require("client is not fully closed yet", !ctx->client.is_closed());
 
-               ctx->client.received_data(vars.get_req_bin("Server_CloseNotify"));
+               ctx->client.received_data(vars.get_req_bin("Record_Server_CloseNotify"));
 
                result.confirm("client connection was terminated", ctx->client.is_closed());
                }),
@@ -1234,7 +1396,20 @@ class Test_TLS_RFC8448 final : public Text_Based_Test
          }
    };
 
-BOTAN_REGISTER_TEST("tls", "tls_rfc8448", Test_TLS_RFC8448);
+class Test_TLS_RFC8448_Server : public Test_TLS_RFC8448
+   {
+   private:
+      std::string side() const override { return "Server"; }
+
+      std::vector<Test::Result> simple_1_rtt(const VarMap& /*vars*/) override { return {}; }
+      std::vector<Test::Result> resumed_handshake_with_0_rtt(const VarMap& /*vars*/) override { return {}; }
+      std::vector<Test::Result> hello_retry_request(const VarMap& /*vars*/) override { return {}; }
+      std::vector<Test::Result> client_authentication(const VarMap& /*vars*/) override { return {}; }
+      std::vector<Test::Result> middlebox_compatibility(const VarMap& /*vars*/) override { return {}; }
+   };
+
+BOTAN_REGISTER_TEST("tls", "tls_rfc8448_client", Test_TLS_RFC8448_Client);
+BOTAN_REGISTER_TEST("tls", "tls_rfc8448_server", Test_TLS_RFC8448_Server);
 
 #endif
 
diff --git a/src/tests/test_tls_stream_integration.cpp b/src/tests/test_tls_stream_integration.cpp
index 6b3efae38..de1617c88 100644
--- a/src/tests/test_tls_stream_integration.cpp
+++ b/src/tests/test_tls_stream_integration.cpp
@@ -1,7 +1,8 @@
 /*
 * TLS ASIO Stream Client-Server Interaction Test
 * (C) 2018-2020 Jack Lloyd
-*     2018-2020 Hannes Rantzsch, Rene Meusel
+*     2018-2020 Hannes Rantzsch, René Meusel
+*     2022      René Meusel, Rohde & Schwarz Cybersecurity
 *
 * Botan is released under the Simplified BSD License (see license.txt)
 */
@@ -15,11 +16,14 @@
 #if BOOST_VERSION >= 106600
 
 #include <functional>
+#include <thread>
+#include <optional>
 
 #include <botan/asio_stream.h>
 #include <botan/auto_rng.h>
 
 #include <boost/asio.hpp>
+#include <boost/exception/exception.hpp>
 
 #include "../cli/tls_helpers.h"  // for Basic_Credentials_Manager
 
@@ -33,36 +37,59 @@ using ssl_stream = Botan::TLS::Stream<net::ip::tcp::socket>;
 using namespace std::placeholders;
 using Result = Botan_Tests::Test::Result;
 
-static const auto k_timeout = std::chrono::seconds(3);
-static const auto k_endpoints = std::vector<tcp::endpoint> {tcp::endpoint{net::ip::make_address("127.0.0.1"), 8082}};
+static const auto k_timeout = std::chrono::seconds(30);
+static const auto k_endpoints = std::vector<tcp::endpoint>
+   {
+   tcp::endpoint{net::ip::make_address("127.0.0.1"), 8082}
+   };
 
 enum { max_msg_length = 512 };
 
-static std::string server_cert() { return Botan_Tests::Test::data_dir() + "/x509/certstor/cert1.crt"; }
-static std::string server_key() { return Botan_Tests::Test::data_dir() + "/x509/certstor/key01.pem"; }
+static std::string server_cert()
+   {
+   return Botan_Tests::Test::data_dir() + "/x509/certstor/cert1.crt";
+   }
+static std::string server_key()
+   {
+   return Botan_Tests::Test::data_dir() + "/x509/certstor/key01.pem";
+   }
 
 class Timeout_Exception : public std::runtime_error
    {
       using std::runtime_error::runtime_error;
    };
 
-class Side
+class Peer
    {
    public:
-      Side()
+      Peer(Botan::TLS::Policy& policy, net::io_context& ioc)
          : m_credentials_manager(true, ""),
-           m_ctx(m_credentials_manager, m_rng, m_session_mgr, m_policy, Botan::TLS::Server_Information()) {}
+           m_ctx(m_credentials_manager, m_rng, m_session_mgr, policy, Botan::TLS::Server_Information()),
+           m_timeout_timer(ioc) {}
 
-      Side(const std::string& server_cert, const std::string& server_key)
+      Peer(Botan::TLS::Policy& policy, net::io_context& ioc, const std::string& server_cert, const std::string& server_key)
          : m_credentials_manager(server_cert, server_key),
-           m_ctx(m_credentials_manager, m_rng, m_session_mgr, m_policy, Botan::TLS::Server_Information()) {}
+           m_ctx(m_credentials_manager, m_rng, m_session_mgr, policy, Botan::TLS::Server_Information()),
+           m_timeout_timer(ioc) {}
 
-      virtual ~Side() {}
+      virtual ~Peer()
+         {
+         cancel_timeout();
+         }
 
-      net::mutable_buffer buffer() { return net::buffer(m_data, max_msg_length); }
-      net::mutable_buffer buffer(size_t size) { return net::buffer(m_data, size); }
+      net::mutable_buffer buffer()
+         {
+         return net::buffer(m_data, max_msg_length);
+         }
+      net::mutable_buffer buffer(size_t size)
+         {
+         return net::buffer(m_data, size);
+         }
 
-      std::string message() const { return std::string(m_data); }
+      std::string message() const
+         {
+         return std::string(m_data);
+         }
 
       // This is a CompletionCondition for net::async_read().
       // Our toy protocol always expects a single \0-terminated string.
@@ -82,13 +109,45 @@ class Side
          return max_msg_length - bytes_transferred;
          }
 
+      void on_timeout(std::function<void(const std::string&)> cb)
+         {
+         m_on_timeout = std::move(cb);
+         }
+
+      void reset_timeout(std::string message)
+         {
+         m_timeout_timer.expires_after(k_timeout);
+         m_timeout_timer.async_wait([=, this](const error_code &ec)
+            {
+            if(ec != net::error::operation_aborted)  // timer cancelled
+               {
+               if(m_on_timeout)
+                  { m_on_timeout(message); }
+
+               // hard-close the underlying transport to maximize the
+               // probability for all participating peers to error out
+               // of their pending I/O operations
+               if(m_stream)
+                  { m_stream->lowest_layer().close(); }
+
+               throw Timeout_Exception("timeout occured: " + message);
+               }
+            });
+         }
+
+      void cancel_timeout()
+         {
+         m_timeout_timer.cancel();
+         }
+
    protected:
       Botan::AutoSeeded_RNG m_rng;
       Basic_Credentials_Manager m_credentials_manager;
       Botan::TLS::Session_Manager_Noop m_session_mgr;
-      Botan::TLS::Policy m_policy;
       Botan::TLS::Context m_ctx;
       std::unique_ptr<ssl_stream> m_stream;
+      net::system_timer m_timeout_timer;
+      std::function<void(const std::string&)> m_on_timeout;
 
       char m_data[max_msg_length];
    };
@@ -96,26 +155,11 @@ class Side
 class Result_Wrapper
    {
    public:
-      Result_Wrapper(net::io_context& ioc, const std::string& name) : m_timer(ioc), m_result(name) {}
-
-      Result& result() { return m_result; }
-
-      void set_timer(const std::string& msg)
-         {
-         m_timer.expires_after(k_timeout);
-         m_timer.async_wait([this, msg](const error_code &ec)
-            {
-            if(ec != net::error::operation_aborted)  // timer cancelled
-               {
-               m_result.test_failure(m_result.who() + ": timeout in " + msg);
-               throw Timeout_Exception(m_result.who());
-               }
-            });
-         }
+      Result_Wrapper(std::string name) : m_result(std::move(name)) {}
 
-      void stop_timer()
+      Result& result()
          {
-         m_timer.cancel();
+         return m_result;
          }
 
       void expect_success(const std::string& msg, const error_code& ec)
@@ -127,9 +171,13 @@ class Result_Wrapper
       void expect_ec(const std::string& msg, const error_code& expected, const error_code& ec)
          {
          if(ec != expected)
-            { m_result.test_failure(msg, "Unexpected error code: " + ec.message()); }
+            {
+            m_result.test_failure(msg, "Unexpected error code: " + ec.message());
+            }
          else
-            { m_result.test_success(msg); }
+            {
+            m_result.test_success(msg);
+            }
          }
 
       void confirm(const std::string& msg, bool condition)
@@ -143,19 +191,21 @@ class Result_Wrapper
          }
 
    private:
-      net::system_timer m_timer;
       Result m_result;
    };
 
-class Server : public Side, public std::enable_shared_from_this<Server>
+class Server : public Peer, public std::enable_shared_from_this<Server>
    {
    public:
-      Server(net::io_context& ioc)
-         : Side(server_cert(), server_key()),
+      Server(Botan::TLS::Policy& policy, net::io_context& ioc, std::string test_name)
+         : Peer(policy, ioc, server_cert(), server_key()),
            m_acceptor(ioc),
-           m_result(ioc, "Server"),
+           m_result("Server (" + test_name + ")"),
            m_short_read_expected(false),
-           m_move_before_accept(false) {}
+           m_move_before_accept(false)
+         {
+         reset_timeout("startup");
+         }
 
       // Control messages
       // The messages below can be used by the test clients in order to configure the server's behavior during a test
@@ -178,7 +228,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
 
          m_result.expect_success("listen", ec);
 
-         m_result.set_timer("accept");
+         reset_timeout("accept");
          m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
          }
 
@@ -192,7 +242,10 @@ class Server : public Side, public std::enable_shared_from_this<Server>
          m_move_before_accept = true;
          }
 
-      Result result() { return m_result.result(); }
+      Result_Wrapper result()
+         {
+         return m_result;
+         }
 
    private:
       void start_session(const error_code& ec, tcp::socket socket)
@@ -203,7 +256,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
          // Note: If this was a real server, we should create a new session (with its own stream) for each accepted
          // connection. In this test we only have one connection.
 
-         if (m_move_before_accept)
+         if(m_move_before_accept)
             {
             // regression test for #2635
             ssl_stream s(std::move(socket), m_ctx);
@@ -214,7 +267,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
             m_stream = std::unique_ptr<ssl_stream>(new ssl_stream(std::move(socket), m_ctx));
             }
 
-         m_result.set_timer("handshake");
+         reset_timeout("handshake");
          m_stream->async_handshake(Botan::TLS::Connection_Side::SERVER,
                                    std::bind(&Server::handle_handshake, shared_from_this(), _1));
          }
@@ -227,7 +280,6 @@ class Server : public Side, public std::enable_shared_from_this<Server>
          handle_write(error_code{});
          }
 
-
       void handle_handshake(const error_code& ec)
          {
          m_result.expect_success("handshake", ec);
@@ -237,7 +289,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
       void handle_write(const error_code& ec)
          {
          m_result.expect_success("send_response", ec);
-         m_result.set_timer("read_message");
+         reset_timeout("read_message");
          net::async_read(*m_stream, buffer(),
                          std::bind(&Server::received_zero_byte, shared_from_this(), _1, _2),
                          std::bind(&Server::handle_read, shared_from_this(), _1, _2));
@@ -248,7 +300,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
          if(m_short_read_expected)
             {
             m_result.expect_ec("received stream truncated error", Botan::TLS::StreamTruncated, ec);
-            quit();
+            cancel_timeout();
             return;
             }
 
@@ -257,13 +309,13 @@ class Server : public Side, public std::enable_shared_from_this<Server>
             if(m_stream->shutdown_received())
                {
                m_result.expect_ec("received EOF after close_notify", net::error::eof, ec);
-               m_result.set_timer("shutdown");
+               reset_timeout("shutdown");
                m_stream->async_shutdown(std::bind(&Server::handle_shutdown, shared_from_this(), _1));
                }
             else
                {
                m_result.test_failure("Unexpected error code: " + ec.message());
-               quit();
+               cancel_timeout();
                }
             return;
             }
@@ -282,7 +334,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
             m_short_read_expected = true;
             }
 
-         m_result.set_timer("send_response");
+         reset_timeout("send_response");
          net::async_write(*m_stream, buffer(bytes_transferred),
                           std::bind(&Server::handle_write, shared_from_this(), _1));
          }
@@ -290,12 +342,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
       void handle_shutdown(const error_code& ec)
          {
          m_result.expect_success("shutdown", ec);
-         quit();
-         }
-
-      void quit()
-         {
-         m_result.stop_timer();
+         cancel_timeout();
          }
 
    private:
@@ -307,7 +354,7 @@ class Server : public Side, public std::enable_shared_from_this<Server>
       bool m_move_before_accept;
    };
 
-class Client : public Side
+class Client : public Peer
    {
       static void accept_all(
          const std::vector<Botan::X509_Certificate>&,
@@ -316,14 +363,17 @@ class Client : public Side
          const std::string&, const Botan::TLS::Policy&) {}
 
    public:
-      Client(net::io_context& ioc)
-         : Side()
+      Client(Botan::TLS::Policy& policy, net::io_context& ioc)
+         : Peer(policy, ioc)
          {
          m_ctx.set_verify_callback(accept_all);
          m_stream = std::unique_ptr<ssl_stream>(new ssl_stream(ioc, m_ctx));
          }
 
-      ssl_stream& stream() {return *m_stream; }
+      ssl_stream& stream()
+         {
+         return *m_stream;
+         }
 
       void close_socket()
          {
@@ -342,20 +392,53 @@ class Client : public Side
 class TestBase
    {
    public:
-      TestBase(net::io_context& ioc, std::shared_ptr<Server> server, const std::string& name)
-         : m_client(ioc),
-           m_server(server),
-           m_result(ioc, name) {}
+      TestBase(net::io_context& ioc, Botan::TLS::Policy& client_policy, Botan::TLS::Policy& server_policy,
+               const std::string& name, const std::string& config_name)
+         : m_name(name + " (" + config_name + ")"),
+           m_client(std::make_shared<Client>(client_policy, ioc)),
+           m_server(std::make_shared<Server>(server_policy, ioc, m_name)),
+           m_result(m_name)
+         {
+         m_client->on_timeout([=, this](const std::string& msg)
+            {
+            m_result.test_failure("timeout in client during: " + msg);
+            });
+         m_server->on_timeout([=, this](const std::string& msg)
+            {
+            m_result.test_failure("timeout in server during: " + msg);
+            });
+
+         m_server->listen();
+         }
 
       virtual ~TestBase() = default;
 
       virtual void finishAsynchronousWork() {}
 
-      Result result() { return m_result.result(); }
+      void fail(const std::string& msg)
+         {
+         m_result.test_failure(msg);
+         }
+
+      void extend_results(std::vector<Result>& results)
+         {
+         results.push_back(m_result.result());
+         results.push_back(m_server->result().result());
+         }
 
    protected:
-      Client m_client;
+      //! retire client and server instances after a job well done
+      void teardown()
+         {
+         m_client->cancel_timeout();
+         }
+
+   protected:
+      std::string m_name;
+
+      std::shared_ptr<Client> m_client;
       std::shared_ptr<Server> m_server;
+
       Result_Wrapper m_result;
    };
 
@@ -371,7 +454,17 @@ class Synchronous_Test : public TestBase
 
       void run(const error_code&)
          {
-         m_client_thread = std::thread(std::bind(&Synchronous_Test::run_synchronous_client, this));
+         m_client_thread = std::thread([this]
+            {
+            try
+               { this->run_synchronous_client(); }
+            catch(const std::exception& ex)
+               { m_result.test_failure(std::string("sync client failed with: ") + ex.what()); }
+            catch(const boost::exception&)
+               { m_result.test_failure("sync client failed with boost exception"); }
+            catch(...)
+               { m_result.test_failure("sync client failed with unknown error"); }
+            });
          }
 
       virtual void run_synchronous_client() = 0;
@@ -389,8 +482,9 @@ class Synchronous_Test : public TestBase
 class Test_Conversation : public TestBase, public net::coroutine, public std::enable_shared_from_this<Test_Conversation>
    {
    public:
-      Test_Conversation(net::io_context& ioc, std::shared_ptr<Server> server, std::string test_name="Test Conversation")
-         : TestBase(ioc, server, test_name) {}
+      Test_Conversation(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                        Botan::TLS::Policy& server_policy, std::string test_name="Test Conversation")
+         : TestBase(ioc, client_policy, server_policy, test_name, config_name) {}
 
       void run(const error_code& ec)
          {
@@ -399,41 +493,41 @@ class Test_Conversation : public TestBase, public net::coroutine, public std::en
 
          reenter(*this)
             {
-            m_result.set_timer("connect");
-            yield net::async_connect(m_client.stream().lowest_layer(), k_endpoints,
+            m_client->reset_timeout("connect");
+            yield net::async_connect(m_client->stream().lowest_layer(), k_endpoints,
                                      std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("connect", ec);
 
-            m_result.set_timer("handshake");
-            yield m_client.stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
-                                                    std::bind(test_case, shared_from_this(), _1));
+            m_client->reset_timeout("handshake");
+            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
+                  std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("handshake", ec);
 
-            m_result.set_timer("send_message");
-            yield net::async_write(m_client.stream(),
+            m_client->reset_timeout("send_message");
+            yield net::async_write(m_client->stream(),
                                    net::buffer(message.c_str(), message.size() + 1), // including \0 termination
                                    std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("send_message", ec);
 
-            m_result.set_timer("receive_response");
-            yield net::async_read(m_client.stream(),
-                                  m_client.buffer(),
-                                  std::bind(&Client::received_zero_byte, &m_client, _1, _2),
+            m_client->reset_timeout("receive_response");
+            yield net::async_read(m_client->stream(),
+                                  m_client->buffer(),
+                                  std::bind(&Client::received_zero_byte, m_client.get(), _1, _2),
                                   std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("receive_response", ec);
-            m_result.confirm("correct message", m_client.message() == message);
+            m_result.confirm("correct message", m_client->message() == message);
 
-            m_result.set_timer("shutdown");
-            yield m_client.stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
+            m_client->reset_timeout("shutdown");
+            yield m_client->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("shutdown", ec);
 
-            m_result.set_timer("await close_notify");
-            yield net::async_read(m_client.stream(), m_client.buffer(),
+            m_client->reset_timeout("await close_notify");
+            yield net::async_read(m_client->stream(), m_client->buffer(),
                                   std::bind(test_case, shared_from_this(), _1));
-            m_result.confirm("received close_notify", m_client.stream().shutdown_received());
+            m_result.confirm("received close_notify", m_client->stream().shutdown_received());
             m_result.expect_ec("closed with EOF", net::error::eof, ec);
 
-            m_result.stop_timer();
+            teardown();
             }
          }
    };
@@ -441,38 +535,41 @@ class Test_Conversation : public TestBase, public net::coroutine, public std::en
 class Test_Conversation_Sync : public Synchronous_Test
    {
    public:
-      Test_Conversation_Sync(net::io_context& ioc, std::shared_ptr<Server> server)
-         : Synchronous_Test(ioc, server, "Test Conversation Sync") {}
+      Test_Conversation_Sync(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                             Botan::TLS::Policy& server_policy)
+         : Synchronous_Test(ioc, client_policy, server_policy, "Test Conversation Sync", config_name) {}
 
       void run_synchronous_client() override
          {
          const std::string message("Time is an illusion. Lunchtime doubly so.");
          error_code ec;
 
-         net::connect(m_client.stream().lowest_layer(), k_endpoints, ec);
+         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
          m_result.expect_success("connect", ec);
 
-         m_client.stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
+         m_client->stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
          m_result.expect_success("handshake", ec);
 
-         net::write(m_client.stream(),
+         net::write(m_client->stream(),
                     net::buffer(message.c_str(), message.size() + 1), // including \0 termination
                     ec);
          m_result.expect_success("send_message", ec);
 
-         net::read(m_client.stream(),
-                   m_client.buffer(),
-                   std::bind(&Client::received_zero_byte, &m_client, _1, _2),
+         net::read(m_client->stream(),
+                   m_client->buffer(),
+                   std::bind(&Client::received_zero_byte, m_client.get(), _1, _2),
                    ec);
          m_result.expect_success("receive_response", ec);
-         m_result.confirm("correct message", m_client.message() == message);
+         m_result.confirm("correct message", m_client->message() == message);
 
-         m_client.stream().shutdown(ec);
+         m_client->stream().shutdown(ec);
          m_result.expect_success("shutdown", ec);
 
-         net::read(m_client.stream(), m_client.buffer(), ec);
-         m_result.confirm("received close_notify", m_client.stream().shutdown_received());
+         net::read(m_client->stream(), m_client->buffer(), ec);
+         m_result.confirm("received close_notify", m_client->stream().shutdown_received());
          m_result.expect_ec("closed with EOF", net::error::eof, ec);
+
+         teardown();
          }
    };
 
@@ -483,31 +580,33 @@ class Test_Conversation_Sync : public Synchronous_Test
 class Test_Eager_Close : public TestBase, public net::coroutine, public std::enable_shared_from_this<Test_Eager_Close>
    {
    public:
-      Test_Eager_Close(net::io_context& ioc, std::shared_ptr<Server> server)
-         : TestBase(ioc, server, "Test Eager Close") {}
+      Test_Eager_Close(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                       Botan::TLS::Policy& server_policy)
+         : TestBase(ioc, client_policy, server_policy, "Test Eager Close", config_name) {}
 
       void run(const error_code& ec)
          {
          static auto test_case = &Test_Eager_Close::run;
          reenter(*this)
             {
-            m_result.set_timer("connect");
-            yield net::async_connect(m_client.stream().lowest_layer(), k_endpoints,
+            m_client->reset_timeout("connect");
+            yield net::async_connect(m_client->stream().lowest_layer(), k_endpoints,
                                      std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("connect", ec);
 
-            m_result.set_timer("handshake");
-            yield m_client.stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
-                                                    std::bind(test_case, shared_from_this(), _1));
+            m_client->reset_timeout("handshake");
+            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
+                  std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("handshake", ec);
 
-            m_result.set_timer("shutdown");
-            yield m_client.stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
+            m_client->reset_timeout("shutdown");
+            yield m_client->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("shutdown", ec);
-            m_result.stop_timer();
 
-            m_client.close_socket();
-            m_result.confirm("did not receive close_notify", !m_client.stream().shutdown_received());
+            m_client->close_socket();
+            m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
+
+            teardown();
             }
          }
    };
@@ -515,24 +614,27 @@ class Test_Eager_Close : public TestBase, public net::coroutine, public std::ena
 class Test_Eager_Close_Sync : public Synchronous_Test
    {
    public:
-      Test_Eager_Close_Sync(net::io_context& ioc, std::shared_ptr<Server> server)
-         : Synchronous_Test(ioc, server, "Test Eager Close Sync") {}
+      Test_Eager_Close_Sync(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                            Botan::TLS::Policy& server_policy)
+         : Synchronous_Test(ioc, client_policy, server_policy, "Test Eager Close Sync", config_name) {}
 
       void run_synchronous_client() override
          {
          error_code ec;
 
-         net::connect(m_client.stream().lowest_layer(), k_endpoints, ec);
+         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
          m_result.expect_success("connect", ec);
 
-         m_client.stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
+         m_client->stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
          m_result.expect_success("handshake", ec);
 
-         m_client.stream().shutdown(ec);
+         m_client->stream().shutdown(ec);
          m_result.expect_success("shutdown", ec);
 
-         m_client.close_socket();
-         m_result.confirm("did not receive close_notify", !m_client.stream().shutdown_received());
+         m_client->close_socket();
+         m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
+
+         teardown();
          }
    };
 
@@ -545,44 +647,45 @@ class Test_Close_Without_Shutdown
      public std::enable_shared_from_this<Test_Close_Without_Shutdown>
    {
    public:
-      Test_Close_Without_Shutdown(net::io_context& ioc, std::shared_ptr<Server> server)
-         : TestBase(ioc, server, "Test Close Without Shutdown") {}
+      Test_Close_Without_Shutdown(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                                  Botan::TLS::Policy& server_policy)
+         : TestBase(ioc, client_policy, server_policy, "Test Close Without Shutdown", config_name) {}
 
       void run(const error_code& ec)
          {
          static auto test_case = &Test_Close_Without_Shutdown::run;
          reenter(*this)
             {
-            m_result.set_timer("connect");
-            yield net::async_connect(m_client.stream().lowest_layer(), k_endpoints,
+            m_client->reset_timeout("connect");
+            yield net::async_connect(m_client->stream().lowest_layer(), k_endpoints,
                                      std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("connect", ec);
 
-            m_result.set_timer("handshake");
-            yield m_client.stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
-                                                    std::bind(test_case, shared_from_this(), _1));
+            m_client->reset_timeout("handshake");
+            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
+                  std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("handshake", ec);
 
             // send the control message to configure the server to expect a short-read
-            m_result.set_timer("send expect_short_read message");
-            yield net::async_write(m_client.stream(),
+            m_client->reset_timeout("send expect_short_read message");
+            yield net::async_write(m_client->stream(),
                                    net::buffer(m_server->expect_short_read_message.c_str(),
                                                m_server->expect_short_read_message.size() + 1), // including \0 termination
                                    std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("send expect_short_read message", ec);
 
             // read the confirmation of the control message above
-            m_result.set_timer("receive_response");
-            yield net::async_read(m_client.stream(),
-                                  m_client.buffer(),
-                                  std::bind(&Client::received_zero_byte, &m_client, _1, _2),
+            m_client->reset_timeout("receive_response");
+            yield net::async_read(m_client->stream(),
+                                  m_client->buffer(),
+                                  std::bind(&Client::received_zero_byte, m_client.get(), _1, _2),
                                   std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("receive_response", ec);
 
-            m_result.stop_timer();
+            m_client->close_socket();
+            m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
 
-            m_client.close_socket();
-            m_result.confirm("did not receive close_notify", !m_client.stream().shutdown_received());
+            teardown();
             }
          }
    };
@@ -590,22 +693,25 @@ class Test_Close_Without_Shutdown
 class Test_Close_Without_Shutdown_Sync : public Synchronous_Test
    {
    public:
-      Test_Close_Without_Shutdown_Sync(net::io_context& ioc, std::shared_ptr<Server> server)
-         : Synchronous_Test(ioc, server, "Test Close Without Shutdown Sync") {}
+      Test_Close_Without_Shutdown_Sync(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                                       Botan::TLS::Policy& server_policy)
+         : Synchronous_Test(ioc, client_policy, server_policy, "Test Close Without Shutdown Sync", config_name) {}
 
       void run_synchronous_client() override
          {
          error_code ec;
-         net::connect(m_client.stream().lowest_layer(), k_endpoints, ec);
+         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
          m_result.expect_success("connect", ec);
 
-         m_client.stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
+         m_client->stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
          m_result.expect_success("handshake", ec);
 
          m_server->expect_short_read();
 
-         m_client.close_socket();
-         m_result.confirm("did not receive close_notify", !m_client.stream().shutdown_received());
+         m_client->close_socket();
+         m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
+
+         teardown();
          }
    };
 
@@ -617,43 +723,44 @@ class Test_No_Shutdown_Response : public TestBase, public net::coroutine,
    public std::enable_shared_from_this<Test_No_Shutdown_Response>
    {
    public:
-      Test_No_Shutdown_Response(net::io_context& ioc, std::shared_ptr<Server> server)
-         : TestBase(ioc, server, "Test No Shutdown Response") {}
+      Test_No_Shutdown_Response(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                                Botan::TLS::Policy& server_policy)
+         : TestBase(ioc, client_policy, server_policy, "Test No Shutdown Response", config_name) {}
 
       void run(const error_code& ec)
          {
          static auto test_case = &Test_No_Shutdown_Response::run;
          reenter(*this)
             {
-            m_result.set_timer("connect");
-            yield net::async_connect(m_client.stream().lowest_layer(), k_endpoints,
+            m_client->reset_timeout("connect");
+            yield net::async_connect(m_client->stream().lowest_layer(), k_endpoints,
                                      std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("connect", ec);
 
-            m_result.set_timer("handshake");
-            yield m_client.stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
-                                                    std::bind(test_case, shared_from_this(), _1));
+            m_client->reset_timeout("handshake");
+            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::CLIENT,
+                  std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("handshake", ec);
 
             // send a control message to make the server shut down
-            m_result.set_timer("send shutdown message");
-            yield net::async_write(m_client.stream(),
+            m_client->reset_timeout("send shutdown message");
+            yield net::async_write(m_client->stream(),
                                    net::buffer(m_server->prepare_shutdown_no_response_message.c_str(),
                                                m_server->prepare_shutdown_no_response_message.size() + 1), // including \0 termination
                                    std::bind(test_case, shared_from_this(), _1));
             m_result.expect_success("send shutdown message", ec);
 
             // read the server's close-notify message
-            m_result.set_timer("read close_notify");
-            yield net::async_read(m_client.stream(), m_client.buffer(),
+            m_client->reset_timeout("read close_notify");
+            yield net::async_read(m_client->stream(), m_client->buffer(),
                                   std::bind(test_case, shared_from_this(), _1));
             m_result.expect_ec("read gives EOF", net::error::eof, ec);
-            m_result.confirm("received close_notify", m_client.stream().shutdown_received());
-
-            m_result.stop_timer();
+            m_result.confirm("received close_notify", m_client->stream().shutdown_received());
 
             // close the socket rather than shutting down
-            m_client.close_socket();
+            m_client->close_socket();
+
+            teardown();
             }
          }
    };
@@ -661,66 +768,106 @@ class Test_No_Shutdown_Response : public TestBase, public net::coroutine,
 class Test_No_Shutdown_Response_Sync : public Synchronous_Test
    {
    public:
-      Test_No_Shutdown_Response_Sync(net::io_context& ioc, std::shared_ptr<Server> server)
-         : Synchronous_Test(ioc, server, "Test No Shutdown Response Sync") {}
+      Test_No_Shutdown_Response_Sync(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                                     Botan::TLS::Policy& server_policy)
+         : Synchronous_Test(ioc, client_policy, server_policy, "Test No Shutdown Response Sync", config_name) {}
 
       void run_synchronous_client() override
          {
          error_code ec;
-         net::connect(m_client.stream().lowest_layer(), k_endpoints, ec);
+         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
          m_result.expect_success("connect", ec);
 
-         m_client.stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
+         m_client->stream().handshake(Botan::TLS::Connection_Side::CLIENT, ec);
          m_result.expect_success("handshake", ec);
 
-         net::write(m_client.stream(),
+         net::write(m_client->stream(),
                     net::buffer(m_server->prepare_shutdown_no_response_message.c_str(),
                                 m_server->prepare_shutdown_no_response_message.size() + 1), // including \0 termination
                     ec);
          m_result.expect_success("send expect_short_read message", ec);
 
-         net::read(m_client.stream(), m_client.buffer(), ec);
+         net::read(m_client->stream(), m_client->buffer(), ec);
          m_result.expect_ec("read gives EOF", net::error::eof, ec);
-         m_result.confirm("received close_notify", m_client.stream().shutdown_received());
+         m_result.confirm("received close_notify", m_client->stream().shutdown_received());
 
          // close the socket rather than shutting down
-         m_client.close_socket();
+         m_client->close_socket();
+
+         teardown();
          }
    };
 
 class Test_Conversation_With_Move : public Test_Conversation
    {
    public:
-      Test_Conversation_With_Move(net::io_context& ioc, std::shared_ptr<Server> server)
-         : Test_Conversation(ioc, server, "Test Conversation With Move")
+      Test_Conversation_With_Move(net::io_context& ioc, std::string config_name, Botan::TLS::Policy& client_policy,
+                                  Botan::TLS::Policy& server_policy)
+         : Test_Conversation(ioc, std::move(config_name), client_policy, server_policy, "Test Conversation With Move")
          {
-            server->move_before_accept();
+         m_server->move_before_accept();
          }
    };
 
 #include <boost/asio/unyield.hpp>
 
-template<typename TestT>
-void run_test_case(std::vector<Result>& results)
+struct SystemConfiguration
    {
-   net::io_context ioc;
+   std::string name;
 
-   auto s = std::make_shared<Server>(ioc);
-   s->listen();
+   Botan::TLS::Text_Policy client_policy;
+   Botan::TLS::Text_Policy server_policy;
 
-   auto t = std::make_shared<TestT>(ioc, s);
-   t->run(error_code{});
+   SystemConfiguration(std::string n, Botan::TLS::Text_Policy cp, Botan::TLS::Text_Policy sp)
+      : name(std::move(n))
+      , client_policy(std::move(cp))
+      , server_policy(std::move(sp))
+      {}
 
-   try
+   template<typename TestT>
+   void run(std::vector<Result>& results)
       {
-      ioc.run();
-      }
-   catch(Timeout_Exception&) { /* the test result will already contain a failure */ }
+      net::io_context ioc;
+
+      auto t = std::make_shared<TestT>(ioc, name, server_policy, client_policy);
 
-   t->finishAsynchronousWork();
+      t->run(error_code{});
 
-   results.push_back(s->result());
-   results.push_back(t->result());
+      while(true)
+         {
+         try
+            {
+            ioc.run();
+            break;
+            }
+         catch(const Timeout_Exception&)
+            { /* timeout is reported via Test::Result object */ }
+         catch(const boost::exception&)
+            { t->fail("boost exception"); }
+         catch(const std::exception& ex)
+            { t->fail(ex.what()); }
+         }
+
+      t->finishAsynchronousWork();
+      t->extend_results(results);
+      }
+   };
+
+std::vector<SystemConfiguration> get_configurations()
+   {
+   return
+      {
+      SystemConfiguration(
+         "TLS 1.2 only",
+         Botan::TLS::Text_Policy("allow_tls12=true\nallow_tls13=false"),
+         Botan::TLS::Text_Policy("allow_tls12=true\nallow_tls13=false")),
+#if defined(BOTAN_HAS_TLS_13)
+      SystemConfiguration(
+         "TLS 1.2 server, TLS 1.x client",
+         Botan::TLS::Text_Policy("allow_tls12=true\nallow_tls13=true"),
+         Botan::TLS::Text_Policy("allow_tls12=true\nallow_tls13=false")),
+#endif
+      };
    }
 
 }  // namespace
@@ -734,15 +881,19 @@ class Tls_Stream_Integration_Tests final : public Test
          {
          std::vector<Test::Result> results;
 
-         run_test_case<Test_Conversation>(results);
-         run_test_case<Test_Eager_Close>(results);
-         run_test_case<Test_Close_Without_Shutdown>(results);
-         run_test_case<Test_No_Shutdown_Response>(results);
-         run_test_case<Test_Conversation_Sync>(results);
-         run_test_case<Test_Eager_Close_Sync>(results);
-         run_test_case<Test_Close_Without_Shutdown_Sync>(results);
-         run_test_case<Test_No_Shutdown_Response_Sync>(results);
-         run_test_case<Test_Conversation_With_Move>(results);
+         auto configs = get_configurations();
+         for(auto& config : configs)
+            {
+            config.run<Test_Conversation>(results);
+            config.run<Test_Eager_Close>(results);
+            config.run<Test_Close_Without_Shutdown>(results);
+            config.run<Test_No_Shutdown_Response>(results);
+            config.run<Test_Conversation_Sync>(results);
+            config.run<Test_Eager_Close_Sync>(results);
+            config.run<Test_Close_Without_Shutdown_Sync>(results);
+            config.run<Test_No_Shutdown_Response_Sync>(results);
+            config.run<Test_Conversation_With_Move>(results);
+            }
 
          return results;
          }