Repository: randombit/botan
Pull request #3244 (2023-02-01 14:42:41)

[TLS 1.3] Prepare for Session Consolidation
This pulls out some utility stuff from #3150 and #3140 hoping to make it a bit more digestable.

* Explicit protocol downgrade (from 1.3 to 1.2 implementation) if the 1.3-stack finds a 1.2-session to be resumed.
  (this avoids having to query the `Session_Manager` twice, which is a guarantee we need later on)
* TLS 1.2 session tickets are aware of the `lifetime_hint` specified in RFC 5077.
* `TLS::Policy::session_ticket_lifetime()` now returns `std::chrono::seconds`
  along with the necessary internal API adaptions (e.g. `New_Session_Ticket_12()`)
* `TLS::New_Session_Ticket_12/13::ticket_lifetime_hint()` now returns `std::chrono::seconds`
* Rename Extension `Session_Ticket` to `Session_Ticket_Extension`
  (the identifier `Session_Ticket` will be used for a strong type later)
* file system helper: `copy_file(from, to)`
* Test helper: `Test::flatten_result_list(std::vector<std::vector<Test::Result>>) -> std::vector<Test::Result>`
* Test helper: `Test::data_file_as_temporary_copy()` to create a test-private copy of some test file
  (to allow modification of the file in the test)


Patch:
diff --git a/doc/api_ref/filters.rst b/doc/api_ref/filters.rst
index d591dee42..04baebf5d 100644
--- a/doc/api_ref/filters.rst
+++ b/doc/api_ref/filters.rst
@@ -69,7 +69,7 @@ Here's code that uses one of them to encrypt a string with AES::
   InitializationVector iv(rng, 16); // a random 128-bit IV
 
   // The algorithm we want is specified by a string
-  Pipe pipe(get_cipher("AES-128/CBC", key, iv, Cipher_Dir::Encryption));
+  Pipe pipe(get_cipher("AES-128/CBC", key, iv, ENCRYPTION));
 
   pipe.process_msg("secrets");
   pipe.process_msg("more secrets");
@@ -134,7 +134,7 @@ Here's an example using two computational filters::
    SymmetricKey key(rng, 32);
    InitializationVector iv(rng, 16);
 
-   Pipe encryptor(get_cipher("AES/CBC/PKCS7", key, iv, Cipher_Dir::Encryption),
+   Pipe encryptor(get_cipher("AES/CBC/PKCS7", key, iv, ENCRYPTION),
                   new Base64_Encoder);
 
    encryptor.start_msg();
@@ -219,7 +219,7 @@ a case where that is useful::
    // have std::string ciphertext, auth_code, key, iv, mac_key;
 
    Pipe pipe(new Base64_Decoder,
-             get_cipher("AES-128", key, iv, Cipher_Dir::Decryption),
+             get_cipher("AES-128", key, iv, DECRYPTION),
              new Fork(
                 0, // this message gets plaintext
                 new MAC_Filter("HMAC(SHA-1)", mac_key)
@@ -579,7 +579,7 @@ The version that doesn't take an IV is useful for things that don't
 use them, like block ciphers in ECB mode, or most stream ciphers. If
 you specify a cipher spec that does want a IV, and you use the version
 that doesn't take one, an exception will be thrown. The ``dir``
-argument can be either ``Cipher_Dir::Encryption`` or ``Cipher_Dir::Decryption``.
+argument can be either ``ENCRYPTION`` or ``DECRYPTION``.
 
 The cipher_spec is a string that specifies what cipher is to be
 used. The general syntax for "cipher_spec" is "STREAM_CIPHER",
diff --git a/doc/dev_ref/todo.rst b/doc/dev_ref/todo.rst
index bd9ff7f56..bda3e972f 100644
--- a/doc/dev_ref/todo.rst
+++ b/doc/dev_ref/todo.rst
@@ -29,11 +29,15 @@ Improved Ciphers Implementations
 * Vector permute AES only supports little-endian systems; fix for big-endian
 * SM4 using AES-NI (https://github.com/mjosaarinen/sm4ni) or vector permute
 * Poly1305 using AVX2
+* ChaCha using SSSE3
 * Threefish-512 AVX2 would be improved by doing 4-wide execution
 * SHA-512 using BMI2+AVX2
 * Constant time bitsliced DES
 * SIMD evaluation of SHA-2 and SHA-3 compression functions
 * Improved Salsa implementations (SIMD_4x32 and/or AVX2)
+* AVX-512 implementations? (SHACAL2, Serpent, ChaCha)
+* Support parallel execution via thread pool in scrypt
+* AVX2 version of Blakma function in Argon2
 * Add CLMUL/PMULL implementations for CRC24/CRC32
 
 Public Key Crypto, Math
@@ -42,19 +46,30 @@ Public Key Crypto, Math
 * Short vector optimization for BigInt
 * Abstract representation of ECC point elements to allow specific
   implementations of the field arithmetic depending upon the curve.
-* Curves for pairings (BLS12-381)
+* Use NAF (joint sparse form) for ECC multi-exponentiation
+* Curves for pairings (BN-256, BLS12-381)
 * Identity based encryption
 * Paillier homomorphic cryptosystem
+* Socialist Millionaires Protocol (needed for OTRv3)
 * New PAKEs (pending CFRG bakeoff results)
 * New post quantum schemes (pending NIST contest results)
 * SPHINX password store (https://eprint.iacr.org/2018/695)
 * X448 and Ed448
+* Use GLV decomposition to speed up secp256k1 operations
 
 Utility Functions
 ------------------
 
 * Make Memory_Pool more concurrent (currently uses a global lock)
 * Guarded integer type to prevent overflow bugs
+* Add logging callbacks
+* Add latency tracing framework
+
+Multiparty Protocols
+----------------------
+
+* Distributed key generation for DL, RSA
+* Threshold signing, decryption
 
 External Providers, Hardware Support
 ----------------------------------------
@@ -69,14 +84,17 @@ External Providers, Hardware Support
 * Add support VPSUM on big-endian PPC64 (GH #2252)
 * Better TPM support: NVRAM, PCR measurements, sealing
 * Add support for TPM 2.0 hardware
+* Support Intel QuickAssist accelerator cards
 
 TLS
 ----------------------------------------
 
 * Make DTLS support optional at build time
 * Improve/optimize DTLS defragmentation and retransmission
+* Implement logging callbacks for TLS
 * Make RSA optional at build time
 * Make finite field DH optional at build time
+* Authentication using TOFU (sqlite3 storage)
 * Certificate pinning (using TACK?)
 * Certificate Transparency extensions
 * TLS supplemental authorization data (RFC 4680, RFC 5878)
@@ -87,6 +105,9 @@ PKIX
 
 * Further tests of validation API (see GH #785)
 * Test suite for validation of 'real world' cert chains (GH #611)
+* Improve output of X509_Certificate::to_string
+  This is a free-form string for human consumption so the only constraints
+  are being informative and concise. (GH #656)
 * X.509 policy constraints
 * OCSP responder logic
 
@@ -94,10 +115,12 @@ New Protocols / Formats
 ----------------------------------------
 
 * ACME protocol
-* Cryptographic Message Syntax (RFC 5652)
+* PKCS7 / Cryptographic Message Syntax
+* PKCS12 / PFX
+* Off-The-Record v3 https://otr.cypherpunks.ca/
+* Certificate Management Protocol (RFC 5273); requires CMS
 * Fernet symmetric encryption (https://cryptography.io/en/latest/fernet/)
 * RNCryptor format (https://github.com/RNCryptor/RNCryptor)
-* Age format (https://age-encryption.org/v1)
 * Useful OpenPGP subset 1: symmetrically encrypted files.
   Not aiming to process arbitrary OpenPGP, but rather produce
   something that happens to be readable by `gpg` and is relatively
@@ -111,6 +134,11 @@ Cleanups
 * Unicode path support on Windows (GH #1615)
 * The X.509 path validation tests have much duplicated logic
 
+Compat Headers
+----------------
+
+* OpenSSL compatible API headers: EVP, TLS, certificates, etc
+
 New C APIs
 ----------------------------------------
 
@@ -128,6 +156,9 @@ New C APIs
 Build/Test
 ----------------------------------------
 
+* Code signing for Windows installers
+* Test runner python script that captures backtraces and other
+  debug info during CI
 * Support hardcoding all test vectors into the botan-test binary
   so it can run as a standalone item (copied to a device, etc)
 * Run iOS binary under simulator in CI
@@ -136,6 +167,9 @@ Build/Test
   (https://github.com/PeterHuewe/tpm-emulator)
 * Add clang-tidy, clang-analyzer, cppcheck to CI
 * Add support for vxWorks
+* Add support for Fuschia OS
+* Add support for CloudABI
+* Add support for SGX
 
 CLI
 ----------------------------------------
@@ -156,5 +190,6 @@ CLI
 Documentation
 ----------------------------------------
 
-* Always needs help
-
+* X.509 certs, path validation
+* Specific docs covering one major topic (RSA, ECDSA, AES/GCM, ...)
+* Some howto style docs (setting up CA, ...)
diff --git a/doc/migration_guide.rst b/doc/migration_guide.rst
index d16547291..1056403ef 100644
--- a/doc/migration_guide.rst
+++ b/doc/migration_guide.rst
@@ -113,14 +113,10 @@ Use of ``enum class``
 --------------------------------
 
 Several enumerations where modified to become ``enum class``, including
-``DL_Group::Format``, ``CRL_Code``, ``EC_Group_Encoding``, ``Signature_Format``,
-``Cipher_Dir``, ``TLS::Extension_Code``, ``TLS::Connection_Side``,
-``TLS::Record_Type``, and ``TLS::Handshake_Type``
-
-In many cases the enumeration values were renamed from ``SHOUTING_CASE`` to
-``CamelCase``. In cases where the enumeration was commonly used by applications
-(for example ``Signature_Format`` and ``Cipher_Dir``) the old enumeration names
-are retained as deprecated variants.
+``DL_Group::Format``, ``CRL_Code``, ``EC_Group_Encoding``, ``Signature_Format``.
+
+The value of ``Signature_Format`` formerly known as ``IEEE_1363`` is now
+called ``Standard``.
 
 ASN.1 enums
 ---------------
diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp
index f3eaf4c29..48b2bcc62 100644
--- a/src/bogo_shim/bogo_shim.cpp
+++ b/src/bogo_shim/bogo_shim.cpp
@@ -1068,7 +1068,7 @@ class Shim_Policy final : public Botan::TLS::Policy
 
       //bool hide_unknown_users() const override;
 
-      //uint32_t session_ticket_lifetime() const override;
+      //std::chrono::seconds session_ticket_lifetime() const override;
 
       std::vector<uint16_t> srtp_profiles() const override
          {
diff --git a/src/cli/cipher.cpp b/src/cli/cipher.cpp
index 84b699b08..ab48fbeee 100644
--- a/src/cli/cipher.cpp
+++ b/src/cli/cipher.cpp
@@ -47,7 +47,7 @@ class Cipher final : public Command
          const Botan::InitializationVector nonce(nonce_hex);
          const std::vector<uint8_t> ad = Botan::hex_decode(ad_hex);
 
-         auto direction = flag_set("decrypt") ? Botan::Cipher_Dir::Decryption : Botan::Cipher_Dir::Encryption;
+         auto direction = flag_set("decrypt") ? Botan::Cipher_Dir::DECRYPTION : Botan::Cipher_Dir::ENCRYPTION;
 
          auto cipher = Botan::Cipher_Mode::create(cipher_algo, direction);
          if(!cipher)
diff --git a/src/cli/pk_crypt.cpp b/src/cli/pk_crypt.cpp
index 2cfcfb7af..0fbeb186d 100644
--- a/src/cli/pk_crypt.cpp
+++ b/src/cli/pk_crypt.cpp
@@ -54,7 +54,7 @@ class PK_Encrypt final : public Command
          const std::string aead_algo = get_arg("aead");
 
          std::unique_ptr<Botan::AEAD_Mode> aead =
-            Botan::AEAD_Mode::create(aead_algo, Botan::Cipher_Dir::Encryption);
+            Botan::AEAD_Mode::create(aead_algo, Botan::ENCRYPTION);
 
          if(!aead)
             throw CLI_Usage_Error("The AEAD '" + aead_algo + "' is not available");
@@ -190,7 +190,7 @@ class PK_Decrypt final : public Command
             }
 
          std::unique_ptr<Botan::AEAD_Mode> aead =
-            Botan::AEAD_Mode::create_or_throw(aead_algo, Botan::Cipher_Dir::Decryption);
+            Botan::AEAD_Mode::create_or_throw(aead_algo, Botan::DECRYPTION);
 
          const size_t expected_keylen = aead->key_spec().maximum_keylength();
 
diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp
index 096bb5e9c..fb4c740cb 100644
--- a/src/cli/speed.cpp
+++ b/src/cli/speed.cpp
@@ -533,9 +533,9 @@ class Speed final : public Command
                }
 #endif
 #if defined(BOTAN_HAS_CIPHER_MODES)
-            else if(auto enc = Botan::Cipher_Mode::create(algo, Botan::Cipher_Dir::Encryption, provider))
+            else if(auto enc = Botan::Cipher_Mode::create(algo, Botan::ENCRYPTION, provider))
                {
-               auto dec = Botan::Cipher_Mode::create_or_throw(algo, Botan::Cipher_Dir::Decryption, provider);
+               auto dec = Botan::Cipher_Mode::create_or_throw(algo, Botan::DECRYPTION, provider);
                bench_cipher_mode(*enc, *dec, msec, buf_sizes);
                }
 #endif
diff --git a/src/examples/aes_cbc.cpp b/src/examples/aes_cbc.cpp
index e5b59f054..a84241cc0 100644
--- a/src/examples/aes_cbc.cpp
+++ b/src/examples/aes_cbc.cpp
@@ -12,7 +12,8 @@ int main() {
                               "luck. Unfortunately, Dane's luck wasn't as good as his old man's.");
   const std::vector<uint8_t> key = Botan::hex_decode("2B7E151628AED2A6ABF7158809CF4F3C");
 
-  auto enc = Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Encryption);
+  std::unique_ptr<Botan::Cipher_Mode> enc =
+      Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::ENCRYPTION);
   enc->set_key(key);
 
   // generate fresh nonce (IV)
diff --git a/src/lib/compat/sodium/sodium_aead.cpp b/src/lib/compat/sodium/sodium_aead.cpp
index 2c6a35273..0d6f35eaf 100644
--- a/src/lib/compat/sodium/sodium_aead.cpp
+++ b/src/lib/compat/sodium/sodium_aead.cpp
@@ -21,7 +21,7 @@ int sodium_aead_chacha20poly1305_encrypt(uint8_t ctext[],
                                          size_t nonce_len,
                                          const uint8_t key[])
    {
-   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", Cipher_Dir::Encryption);
+   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", ENCRYPTION);
 
    chacha20poly1305->set_key(key, 32);
    chacha20poly1305->set_associated_data(ad, ad_len);
@@ -55,7 +55,7 @@ int sodium_aead_chacha20poly1305_decrypt(uint8_t ptext[],
 
    *ptext_len = 0;
 
-   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", Cipher_Dir::Decryption);
+   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", DECRYPTION);
 
    chacha20poly1305->set_key(key, 32);
    chacha20poly1305->set_associated_data(ad, ad_len);
@@ -90,7 +90,7 @@ int sodium_aead_chacha20poly1305_encrypt_detached(uint8_t ctext[],
                                                   size_t nonce_len,
                                                   const uint8_t key[])
    {
-   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", Cipher_Dir::Encryption);
+   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", ENCRYPTION);
 
    chacha20poly1305->set_key(key, 32);
    chacha20poly1305->set_associated_data(ad, ad_len);
@@ -118,7 +118,7 @@ int sodium_aead_chacha20poly1305_decrypt_detached(uint8_t ptext[],
                                                   size_t nonce_len,
                                                   const uint8_t key[])
    {
-   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", Cipher_Dir::Decryption);
+   auto chacha20poly1305 = AEAD_Mode::create_or_throw("ChaCha20Poly1305", DECRYPTION);
 
    chacha20poly1305->set_key(key, 32);
    chacha20poly1305->set_associated_data(ad, ad_len);
diff --git a/src/lib/ffi/ffi_cipher.cpp b/src/lib/ffi/ffi_cipher.cpp
index ba1f1beb3..3ef572838 100644
--- a/src/lib/ffi/ffi_cipher.cpp
+++ b/src/lib/ffi/ffi_cipher.cpp
@@ -61,9 +61,7 @@ int botan_cipher_init(botan_cipher_t* cipher, const char* cipher_name, uint32_t
    {
    return ffi_guard_thunk(__func__, [=]() -> int {
       const bool encrypt_p = ((flags & BOTAN_CIPHER_INIT_FLAG_MASK_DIRECTION) == BOTAN_CIPHER_INIT_FLAG_ENCRYPT);
-      const Botan::Cipher_Dir dir = encrypt_p ?
-         Botan::Cipher_Dir::Encryption : Botan::Cipher_Dir::Decryption;
-
+      const Botan::Cipher_Dir dir = encrypt_p ? Botan::ENCRYPTION : Botan::DECRYPTION;
       std::unique_ptr<Botan::Cipher_Mode> mode(Botan::Cipher_Mode::create(cipher_name, dir));
       if(!mode)
          return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
diff --git a/src/lib/misc/cryptobox/cryptobox.cpp b/src/lib/misc/cryptobox/cryptobox.cpp
index cdeeb88e3..9384f92df 100644
--- a/src/lib/misc/cryptobox/cryptobox.cpp
+++ b/src/lib/misc/cryptobox/cryptobox.cpp
@@ -73,7 +73,7 @@ std::string encrypt(const uint8_t input[], size_t input_len,
    const uint8_t* iv = mk + CIPHER_KEY_LEN + MAC_KEY_LEN;
 
    // Now encrypt and authenticate
-   std::unique_ptr<Cipher_Mode> ctr = Cipher_Mode::create_or_throw("Serpent/CTR-BE", Cipher_Dir::Encryption);
+   std::unique_ptr<Cipher_Mode> ctr = Cipher_Mode::create_or_throw("Serpent/CTR-BE", ENCRYPTION);
    ctr->set_key(cipher_key, CIPHER_KEY_LEN);
    ctr->start(iv, CIPHER_IV_LEN);
    ctr->finish(out_buf, CRYPTOBOX_HEADER_LEN);
@@ -143,7 +143,7 @@ decrypt_bin(const uint8_t input[], size_t input_len,
    if(!constant_time_compare(computed_mac.data(), box_mac, MAC_OUTPUT_LEN))
       throw Decoding_Error("CryptoBox integrity failure");
 
-   std::unique_ptr<Cipher_Mode> ctr(Cipher_Mode::create_or_throw("Serpent/CTR-BE", Cipher_Dir::Decryption));
+   std::unique_ptr<Cipher_Mode> ctr(Cipher_Mode::create_or_throw("Serpent/CTR-BE", DECRYPTION));
    ctr->set_key(cipher_key, CIPHER_KEY_LEN);
    ctr->start(iv, CIPHER_IV_LEN);
    ctr->finish(ciphertext, CRYPTOBOX_HEADER_LEN);
diff --git a/src/lib/modes/aead/aead.cpp b/src/lib/modes/aead/aead.cpp
index 86eb040d0..a09aedf00 100644
--- a/src/lib/modes/aead/aead.cpp
+++ b/src/lib/modes/aead/aead.cpp
@@ -65,7 +65,7 @@ std::unique_ptr<AEAD_Mode> AEAD_Mode::create(const std::string& algo,
 #if defined(BOTAN_HAS_AEAD_CHACHA20_POLY1305)
    if(algo == "ChaCha20Poly1305")
       {
-      if(dir == Cipher_Dir::Encryption)
+      if(dir == ENCRYPTION)
          return std::make_unique<ChaCha20Poly1305_Encryption>();
       else
          return std::make_unique<ChaCha20Poly1305_Decryption>();
@@ -115,7 +115,7 @@ std::unique_ptr<AEAD_Mode> AEAD_Mode::create(const std::string& algo,
       {
       size_t tag_len = req.arg_as_integer(1, 16);
       size_t L_len = req.arg_as_integer(2, 3);
-      if(dir == Cipher_Dir::Encryption)
+      if(dir == ENCRYPTION)
          return std::make_unique<CCM_Encryption>(std::move(bc), tag_len, L_len);
       else
          return std::make_unique<CCM_Decryption>(std::move(bc), tag_len, L_len);
@@ -126,7 +126,7 @@ std::unique_ptr<AEAD_Mode> AEAD_Mode::create(const std::string& algo,
    if(req.algo_name() == "GCM")
       {
       size_t tag_len = req.arg_as_integer(1, 16);
-      if(dir == Cipher_Dir::Encryption)
+      if(dir == ENCRYPTION)
          return std::make_unique<GCM_Encryption>(std::move(bc), tag_len);
       else
          return std::make_unique<GCM_Decryption>(std::move(bc), tag_len);
@@ -137,7 +137,7 @@ std::unique_ptr<AEAD_Mode> AEAD_Mode::create(const std::string& algo,
    if(req.algo_name() == "OCB")
       {
       size_t tag_len = req.arg_as_integer(1, 16);
-      if(dir == Cipher_Dir::Encryption)
+      if(dir == ENCRYPTION)
          return std::make_unique<OCB_Encryption>(std::move(bc), tag_len);
       else
          return std::make_unique<OCB_Decryption>(std::move(bc), tag_len);
@@ -148,7 +148,7 @@ std::unique_ptr<AEAD_Mode> AEAD_Mode::create(const std::string& algo,
    if(req.algo_name() == "EAX")
       {
       size_t tag_len = req.arg_as_integer(1, bc->block_size());
-      if(dir == Cipher_Dir::Encryption)
+      if(dir == ENCRYPTION)
          return std::make_unique<EAX_Encryption>(std::move(bc), tag_len);
       else
          return std::make_unique<EAX_Decryption>(std::move(bc), tag_len);
@@ -158,7 +158,7 @@ std::unique_ptr<AEAD_Mode> AEAD_Mode::create(const std::string& algo,
 #if defined(BOTAN_HAS_AEAD_SIV)
    if(req.algo_name() == "SIV")
       {
-      if(dir == Cipher_Dir::Encryption)
+      if(dir == ENCRYPTION)
          return std::make_unique<SIV_Encryption>(std::move(bc));
       else
          return std::make_unique<SIV_Decryption>(std::move(bc));
diff --git a/src/lib/modes/aead/aead.h b/src/lib/modes/aead/aead.h
index ec6b42123..3d3dc0ed3 100644
--- a/src/lib/modes/aead/aead.h
+++ b/src/lib/modes/aead/aead.h
@@ -133,7 +133,7 @@ class BOTAN_PUBLIC_API(2,0) AEAD_Mode : public Cipher_Mode
 /**
 * Get an AEAD mode by name (eg "AES-128/GCM" or "Serpent/EAX")
 * @param name AEAD name
-* @param direction Cipher_Dir::Encryption or Cipher_Dir::Decryption
+* @param direction ENCRYPTION or DECRYPTION
 */
 inline AEAD_Mode* get_aead(const std::string& name, Cipher_Dir direction)
    {
diff --git a/src/lib/modes/cipher_mode.cpp b/src/lib/modes/cipher_mode.cpp
index bc0b54f8d..79b297c37 100644
--- a/src/lib/modes/cipher_mode.cpp
+++ b/src/lib/modes/cipher_mode.cpp
@@ -122,7 +122,7 @@ std::unique_ptr<Cipher_Mode> Cipher_Mode::create(const std::string& algo,
 
       if(padding == "CTS")
          {
-         if(direction == Cipher_Dir::Encryption)
+         if(direction == ENCRYPTION)
             return std::make_unique<CTS_Encryption>(std::move(bc));
          else
             return std::make_unique<CTS_Decryption>(std::move(bc));
@@ -133,7 +133,7 @@ std::unique_ptr<Cipher_Mode> Cipher_Mode::create(const std::string& algo,
 
          if(pad)
             {
-            if(direction == Cipher_Dir::Encryption)
+            if(direction == ENCRYPTION)
                return std::make_unique<CBC_Encryption>(std::move(bc), std::move(pad));
             else
                return std::make_unique<CBC_Decryption>(std::move(bc), std::move(pad));
@@ -145,7 +145,7 @@ std::unique_ptr<Cipher_Mode> Cipher_Mode::create(const std::string& algo,
 #if defined(BOTAN_HAS_MODE_XTS)
    if(spec.algo_name() == "XTS")
       {
-      if(direction == Cipher_Dir::Encryption)
+      if(direction == ENCRYPTION)
          return std::make_unique<XTS_Encryption>(std::move(bc));
       else
          return std::make_unique<XTS_Decryption>(std::move(bc));
@@ -156,7 +156,7 @@ std::unique_ptr<Cipher_Mode> Cipher_Mode::create(const std::string& algo,
    if(spec.algo_name() == "CFB")
       {
       const size_t feedback_bits = spec.arg_as_integer(1, 8*bc->block_size());
-      if(direction == Cipher_Dir::Encryption)
+      if(direction == ENCRYPTION)
          return std::make_unique<CFB_Encryption>(std::move(bc), feedback_bits);
       else
          return std::make_unique<CFB_Decryption>(std::move(bc), feedback_bits);
@@ -175,7 +175,7 @@ std::vector<std::string> Cipher_Mode::providers(const std::string& algo_spec)
    std::vector<std::string> providers;
    for(auto&& prov : possible)
       {
-      std::unique_ptr<Cipher_Mode> mode = Cipher_Mode::create(algo_spec, Cipher_Dir::Encryption, prov);
+      std::unique_ptr<Cipher_Mode> mode = Cipher_Mode::create(algo_spec, ENCRYPTION, prov);
       if(mode)
          {
          providers.push_back(prov); // available
diff --git a/src/lib/modes/cipher_mode.h b/src/lib/modes/cipher_mode.h
index c87d9d1dc..a401cb997 100644
--- a/src/lib/modes/cipher_mode.h
+++ b/src/lib/modes/cipher_mode.h
@@ -20,13 +20,7 @@ namespace Botan {
 * The two possible directions for cipher filters, determining whether they
 * actually perform encryption or decryption.
 */
-enum class Cipher_Dir : int {
-   Encryption,
-   Decryption,
-
-   ENCRYPTION BOTAN_DEPRECATED("Use Cipher_Dir::Encryption") = Encryption,
-   DECRYPTION BOTAN_DEPRECATED("Use Cipher_Dir::Decryption") = Decryption,
-};
+enum Cipher_Dir : int { ENCRYPTION, DECRYPTION };
 
 /**
 * Interface for cipher modes
@@ -215,7 +209,7 @@ class BOTAN_PUBLIC_API(2,0) Cipher_Mode : public SymmetricAlgorithm
 /**
 * Get a cipher mode by name (eg "AES-128/CBC" or "Serpent/XTS")
 * @param algo_spec cipher name
-* @param direction Cipher_Dir::Encryption or Cipher_Dir::Decryption
+* @param direction ENCRYPTION or DECRYPTION
 * @param provider provider implementation to choose
 */
 BOTAN_DEPRECATED("Use Cipher_Mode::create")
diff --git a/src/lib/prov/commoncrypto/commoncrypto.h b/src/lib/prov/commoncrypto/commoncrypto.h
index b1e4c2bab..5e17adc0a 100644
--- a/src/lib/prov/commoncrypto/commoncrypto.h
+++ b/src/lib/prov/commoncrypto/commoncrypto.h
@@ -19,7 +19,7 @@ namespace Botan {
 class Cipher_Mode;
 class BlockCipher;
 class HashFunction;
-enum class Cipher_Dir : int;
+enum Cipher_Dir : int;
 typedef int32_t CCCryptorStatus;
 
 class BOTAN_PUBLIC_API(2, 0) CommonCrypto_Error final : public Exception
diff --git a/src/lib/prov/commoncrypto/commoncrypto_mode.cpp b/src/lib/prov/commoncrypto/commoncrypto_mode.cpp
index a0a60bb40..dded367ac 100644
--- a/src/lib/prov/commoncrypto/commoncrypto_mode.cpp
+++ b/src/lib/prov/commoncrypto/commoncrypto_mode.cpp
@@ -159,7 +159,7 @@ size_t CommonCrypto_Cipher_Mode::ideal_granularity() const
 
 size_t CommonCrypto_Cipher_Mode::minimum_final_size() const
    {
-   if(m_direction == Cipher_Dir::Encryption)
+   if(m_direction == ENCRYPTION)
       return 0;
    else
       return m_opts.block_size;
@@ -223,7 +223,7 @@ Key_Length_Specification CommonCrypto_Cipher_Mode::key_spec() const
 void CommonCrypto_Cipher_Mode::key_schedule(const uint8_t key[], size_t length)
    {
    CCCryptorStatus status;
-   CCOperation op = m_direction == Cipher_Dir::Encryption ? kCCEncrypt : kCCDecrypt;
+   CCOperation op = m_direction == ENCRYPTION ? kCCEncrypt : kCCDecrypt;
    status = CCCryptorCreateWithMode(op, m_opts.mode, m_opts.algo, m_opts.padding,
                                     nullptr, key, length, nullptr, 0, 0, 0, &m_cipher);
    if(status != kCCSuccess)
diff --git a/src/lib/pubkey/ecies/ecies.cpp b/src/lib/pubkey/ecies/ecies.cpp
index 47b6c558e..a1142c369 100644
--- a/src/lib/pubkey/ecies/ecies.cpp
+++ b/src/lib/pubkey/ecies/ecies.cpp
@@ -250,7 +250,7 @@ ECIES_Encryptor::ECIES_Encryptor(const PK_Key_Agreement_Key& private_key,
       m_eph_public_key_bin = m_params.domain().OS2ECP(m_eph_public_key_bin).encode(ecies_params.compression_type());
       }
    m_mac = m_params.create_mac();
-   m_cipher = m_params.create_cipher(Cipher_Dir::Encryption);
+   m_cipher = m_params.create_cipher(ENCRYPTION);
    }
 
 /*
@@ -338,7 +338,7 @@ ECIES_Decryptor::ECIES_Decryptor(const PK_Key_Agreement_Key& key,
       }
 
    m_mac = m_params.create_mac();
-   m_cipher = m_params.create_cipher(Cipher_Dir::Decryption);
+   m_cipher = m_params.create_cipher(DECRYPTION);
    }
 
 size_t ECIES_Decryptor::plaintext_length(size_t ctext_len) const
diff --git a/src/lib/pubkey/pbes2/pbes2.cpp b/src/lib/pubkey/pbes2/pbes2.cpp
index a0c5c511c..99706ff76 100644
--- a/src/lib/pubkey/pbes2/pbes2.cpp
+++ b/src/lib/pubkey/pbes2/pbes2.cpp
@@ -216,7 +216,7 @@ pbes2_encrypt_shared(const secure_vector<uint8_t>& key_bits,
    if(cipher_oid.empty())
       throw Encoding_Error("PBE-PKCS5 v2.0: No OID assigned for " + cipher);
 
-   std::unique_ptr<Cipher_Mode> enc = Cipher_Mode::create(cipher, Cipher_Dir::Encryption);
+   std::unique_ptr<Cipher_Mode> enc = Cipher_Mode::create(cipher, ENCRYPTION);
 
    if(!enc)
       throw Decoding_Error("PBE-PKCS5 cannot encrypt no cipher " + cipher);
@@ -320,7 +320,7 @@ pbes2_decrypt(const secure_vector<uint8_t>& key_bits,
    secure_vector<uint8_t> iv;
    BER_Decoder(enc_algo.get_parameters()).decode(iv, ASN1_Type::OctetString).verify_end();
 
-   std::unique_ptr<Cipher_Mode> dec = Cipher_Mode::create(cipher, Cipher_Dir::Decryption);
+   std::unique_ptr<Cipher_Mode> dec = Cipher_Mode::create(cipher, DECRYPTION);
    if(!dec)
       throw Decoding_Error("PBE-PKCS5 cannot decrypt no cipher " + cipher);
 
diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp
index 7c0f7348e..d4b6d5f2e 100644
--- a/src/lib/tls/msg_client_hello.cpp
+++ b/src/lib/tls/msg_client_hello.cpp
@@ -340,12 +340,12 @@ std::vector<Protocol_Version> Client_Hello::supported_versions() const
 
 bool Client_Hello_12::supports_session_ticket() const
    {
-   return m_data->extensions.has<Session_Ticket>();
+   return m_data->extensions.has<Session_Ticket_Extension>();
    }
 
 std::vector<uint8_t> Client_Hello_12::session_ticket() const
    {
-   if(Session_Ticket* ticket = m_data->extensions.get<Session_Ticket>())
+   if(auto* ticket = m_data->extensions.get<Session_Ticket_Extension>())
       { return ticket->contents(); }
    return {};
    }
@@ -475,7 +475,7 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
    if(policy.negotiate_encrypt_then_mac())
       { m_data->extensions.add(new Encrypt_then_MAC); }
 
-   m_data->extensions.add(new Session_Ticket());
+   m_data->extensions.add(new Session_Ticket_Extension());
 
    m_data->extensions.add(new Renegotiation_Extension(reneg_info));
 
@@ -550,7 +550,7 @@ Client_Hello_12::Client_Hello_12(Handshake_IO& io,
    if(session.supports_encrypt_then_mac())
       { m_data->extensions.add(new Encrypt_then_MAC); }
 
-   m_data->extensions.add(new Session_Ticket(session.session_ticket()));
+   m_data->extensions.add(new Session_Ticket_Extension(session.session_ticket()));
 
    m_data->extensions.add(new Renegotiation_Extension(reneg_info));
 
@@ -791,7 +791,7 @@ Client_Hello_13::Client_Hello_13(const Policy& policy,
    if(policy.allow_tls12())
       {
       m_data->extensions.add(new Renegotiation_Extension());
-      m_data->extensions.add(new Session_Ticket());
+      m_data->extensions.add(new Session_Ticket_Extension());
 
       // EMS must always be used with TLS 1.2, regardless of the policy
       m_data->extensions.add(new Extended_Master_Secret);
diff --git a/src/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp
index f28c8089c..97546b4f7 100644
--- a/src/lib/tls/msg_server_hello.cpp
+++ b/src/lib/tls/msg_server_hello.cpp
@@ -285,7 +285,7 @@ Server_Hello_12::Server_Hello_12(Handshake_IO& io,
 
    if(client_hello.supports_session_ticket() && server_settings.offer_session_ticket())
       {
-      m_data->extensions.add(new Session_Ticket());
+      m_data->extensions.add(new Session_Ticket_Extension());
       }
 
    if(m_data->legacy_version.is_datagram_protocol())
@@ -365,7 +365,7 @@ Server_Hello_12::Server_Hello_12(Handshake_IO& io,
 
    if(client_hello.supports_session_ticket() && offer_session_ticket)
       {
-      m_data->extensions.add(new Session_Ticket());
+      m_data->extensions.add(new Session_Ticket_Extension());
       }
 
    cb.tls_modify_extensions(m_data->extensions, Connection_Side::Server, type());
@@ -421,7 +421,7 @@ bool Server_Hello_12::supports_certificate_status_message() const
 
 bool Server_Hello_12::supports_session_ticket() const
    {
-   return m_data->extensions.has<Session_Ticket>();
+   return m_data->extensions.has<Session_Ticket_Extension>();
    }
 
 uint16_t Server_Hello_12::srtp_profile() const
diff --git a/src/lib/tls/msg_session_ticket.cpp b/src/lib/tls/msg_session_ticket.cpp
index d4f1052cd..e150ab8e1 100644
--- a/src/lib/tls/msg_session_ticket.cpp
+++ b/src/lib/tls/msg_session_ticket.cpp
@@ -16,9 +16,9 @@
 namespace Botan::TLS {
 
 New_Session_Ticket_12::New_Session_Ticket_12(Handshake_IO& io,
-                                       Handshake_Hash& hash,
-                                       const std::vector<uint8_t>& ticket,
-                                       uint32_t lifetime) :
+                                             Handshake_Hash& hash,
+                                             const std::vector<uint8_t>& ticket,
+                                             std::chrono::seconds lifetime) :
    m_ticket_lifetime_hint(lifetime),
    m_ticket(ticket)
    {
@@ -38,7 +38,7 @@ New_Session_Ticket_12::New_Session_Ticket_12(const std::vector<uint8_t>& buf)
 
    TLS_Data_Reader reader("SessionTicket", buf);
 
-   m_ticket_lifetime_hint = reader.get_uint32_t();
+   m_ticket_lifetime_hint = std::chrono::seconds(reader.get_uint32_t());
    m_ticket = reader.get_range<uint8_t>(2, 0, 65535);
    reader.assert_done();
    }
@@ -46,7 +46,7 @@ New_Session_Ticket_12::New_Session_Ticket_12(const std::vector<uint8_t>& buf)
 std::vector<uint8_t> New_Session_Ticket_12::serialize() const
    {
    std::vector<uint8_t> buf(4);
-   store_be(m_ticket_lifetime_hint, buf.data());
+   store_be(static_cast<uint32_t>(m_ticket_lifetime_hint.count()), buf.data());
    append_tls_length_value(buf, m_ticket, 2);
    return buf;
    }
@@ -58,12 +58,12 @@ New_Session_Ticket_13::New_Session_Ticket_13(const std::vector<uint8_t>& buf,
    {
    TLS_Data_Reader reader("New_Session_Ticket_13", buf);
 
-   m_ticket_lifetime_hint = reader.get_uint32_t();
+   m_ticket_lifetime_hint = std::chrono::seconds(reader.get_uint32_t());
 
    // RFC 8446 4.6.1
    //    Servers MUST NOT use any value [of ticket_lifetime] greater than 604800
    //    seconds (7 days).
-   if(m_ticket_lifetime_hint > 604800)
+   if(m_ticket_lifetime_hint > std::chrono::days(7))
       {
       throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
                           "Received a session ticket with lifetime longer than one week.");
diff --git a/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp b/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp
index 61efdd380..f30888d46 100644
--- a/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp
+++ b/src/lib/tls/tls12/tls_cbc/tls_cbc.cpp
@@ -45,7 +45,7 @@ TLS_CBC_HMAC_AEAD_Mode::TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir dir,
    m_mac = std::move(mac);
 
    auto null_padding = std::make_unique<Null_Padding>();
-   if(dir == Cipher_Dir::Encryption)
+   if(dir == ENCRYPTION)
       m_cbc.reset(new CBC_Encryption(std::move(cipher), std::move(null_padding)));
    else
       m_cbc.reset(new CBC_Decryption(std::move(cipher), std::move(null_padding)));
diff --git a/src/lib/tls/tls12/tls_cbc/tls_cbc.h b/src/lib/tls/tls12/tls_cbc/tls_cbc.h
index 76248c8b4..6f6bc1f73 100644
--- a/src/lib/tls/tls12/tls_cbc/tls_cbc.h
+++ b/src/lib/tls/tls12/tls_cbc/tls_cbc.h
@@ -117,7 +117,7 @@ class BOTAN_TEST_API TLS_CBC_HMAC_AEAD_Encryption final : public TLS_CBC_HMAC_AE
                              const size_t mac_keylen,
                              const Protocol_Version version,
                              bool use_encrypt_then_mac) :
-         TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir::Encryption,
+         TLS_CBC_HMAC_AEAD_Mode(ENCRYPTION,
                                 std::move(cipher),
                                 std::move(mac),
                                 cipher_keylen,
@@ -152,7 +152,7 @@ class BOTAN_TEST_API TLS_CBC_HMAC_AEAD_Decryption final : public TLS_CBC_HMAC_AE
                                    const size_t mac_keylen,
                                    const Protocol_Version version,
                                    bool use_encrypt_then_mac) :
-         TLS_CBC_HMAC_AEAD_Mode(Cipher_Dir::Decryption,
+         TLS_CBC_HMAC_AEAD_Mode(DECRYPTION,
                                 std::move(cipher),
                                 std::move(mac),
                                 cipher_keylen,
diff --git a/src/lib/tls/tls12/tls_client_impl_12.cpp b/src/lib/tls/tls12/tls_client_impl_12.cpp
index d9b6f47ee..a1efc7d3e 100644
--- a/src/lib/tls/tls12/tls_client_impl_12.cpp
+++ b/src/lib/tls/tls12/tls_client_impl_12.cpp
@@ -33,7 +33,7 @@ class Client_Handshake_State_12 final : public Handshake_State
          return *server_public_key.get();
          }
 
-      bool is_a_resumption() const { return (resumed_session != nullptr); }
+      bool is_a_resumption() const { return (resumed_session.has_value()); }
 
       bool is_a_renegotiation() const { return m_is_reneg; }
 
@@ -51,7 +51,7 @@ class Client_Handshake_State_12 final : public Handshake_State
 
       std::unique_ptr<Public_Key> server_public_key;
       // Used during session resumption
-      std::unique_ptr<Session> resumed_session;
+      std::optional<Session> resumed_session;
       bool m_is_reneg = false;
    };
 }
@@ -74,7 +74,7 @@ Client_Impl_12::Client_Impl_12(Callbacks& callbacks,
    {
    const auto version = datagram ? Protocol_Version::DTLS_V12 : Protocol_Version::TLS_V12;
    Handshake_State& state = create_handshake_state(version);
-   send_client_hello(state, false, version, next_protocols);
+   send_client_hello(state, false, version, std::nullopt /* no a-priori session to resume */, next_protocols);
    }
 
 Client_Impl_12::Client_Impl_12(const Channel_Impl::Downgrade_Information& downgrade_info) :
@@ -90,14 +90,27 @@ Client_Impl_12::Client_Impl_12(const Channel_Impl::Downgrade_Information& downgr
    {
    Handshake_State& state = create_handshake_state(Protocol_Version::TLS_V12);
 
-   std::vector<uint8_t> client_hello_msg(downgrade_info.client_hello_message.begin() + 4 /* handshake header length */,
-                                         downgrade_info.client_hello_message.end());
+   if(!downgrade_info.client_hello_message.empty())
+      {
+      // Downgrade detected after receiving a TLS 1.2 server hello. We need to
+      // recreate the state as if this implementation issued the client hello.
+      std::vector<uint8_t> client_hello_msg(downgrade_info.client_hello_message.begin() + 4 /* handshake header length */,
+                                            downgrade_info.client_hello_message.end());
 
-   state.client_hello(new Client_Hello_12(client_hello_msg));
-   state.hash().update(downgrade_info.client_hello_message);
+      state.client_hello(new Client_Hello_12(client_hello_msg));
+      state.hash().update(downgrade_info.client_hello_message);
 
-   secure_renegotiation_check(state.client_hello());
-   state.set_expected_next(Handshake_Type::ServerHello);
+      secure_renegotiation_check(state.client_hello());
+      state.set_expected_next(Handshake_Type::ServerHello);
+      }
+   else
+      {
+      // Downgrade initiated after a TLS 1.2 session was found. No communication
+      // has happened yet but the found session should be used for resumption.
+      BOTAN_ASSERT_NOMSG(downgrade_info.tls12_session.has_value() &&
+                         downgrade_info.tls12_session->version().is_pre_tls_13());
+      send_client_hello(state, false, downgrade_info.tls12_session->version(), downgrade_info.tls12_session, downgrade_info.next_protocols);
+      }
    }
 
 std::unique_ptr<Handshake_State> Client_Impl_12::new_handshake_state(std::unique_ptr<Handshake_IO> io)
@@ -134,6 +147,7 @@ void Client_Impl_12::initiate_handshake(Handshake_State& state,
 void Client_Impl_12::send_client_hello(Handshake_State& state_base,
                                        bool force_full_renegotiation,
                                        Protocol_Version version,
+                                       std::optional<Session> session_info,
                                        const std::vector<std::string>& next_protocols)
    {
    Client_Handshake_State_12& state = dynamic_cast<Client_Handshake_State_12&>(state_base);
@@ -142,10 +156,17 @@ void Client_Impl_12::send_client_hello(Handshake_State& state_base,
       state.set_expected_next(Handshake_Type::HelloVerifyRequest); // optional
    state.set_expected_next(Handshake_Type::ServerHello);
 
-   if(!force_full_renegotiation && !m_info.empty())
+   if(!force_full_renegotiation)
       {
-      auto session_info = std::make_unique<Session>();
-      if(session_manager().load_from_server_info(m_info, *session_info))
+      // if no session is provided, we need to try and find one opportunistically
+      if(!session_info.has_value() && !m_info.empty())
+         {
+         session_info = Session();
+         if(!session_manager().load_from_server_info(m_info, session_info.value()))
+            { session_info.reset(); }
+         }
+
+      if(session_info.has_value())
          {
          /*
          Ensure that the session protocol cipher and version are acceptable
@@ -373,7 +394,7 @@ void Client_Impl_12::process_handshake_msg(const Handshake_State* active_state,
       if(server_returned_same_session_id)
          {
          // successful resumption
-         BOTAN_ASSERT_NOMSG(state.resumed_session);
+         BOTAN_ASSERT_NOMSG(state.resumed_session.has_value());
 
          /*
          * In this case, we offered the version used in the original
@@ -756,7 +777,13 @@ void Client_Impl_12::process_handshake_msg(const Handshake_State* active_state,
          session_ticket,
          m_info,
          state.server_hello()->srtp_profile(),
-         callbacks().tls_current_timestamp()
+         callbacks().tls_current_timestamp(),
+
+         // Session Tickets (as defined in RFC 5077) contain a lifetime_hint,
+         // sessions identified via a Session_ID do not.
+         ((state.new_session_ticket())
+            ? state.new_session_ticket()->ticket_lifetime_hint()
+            : std::chrono::seconds::max())
          );
 
       const bool should_save = save_session(session_info);
diff --git a/src/lib/tls/tls12/tls_client_impl_12.h b/src/lib/tls/tls12/tls_client_impl_12.h
index a64f4f6c6..347f0fc41 100644
--- a/src/lib/tls/tls12/tls_client_impl_12.h
+++ b/src/lib/tls/tls12/tls_client_impl_12.h
@@ -78,6 +78,7 @@ class Client_Impl_12 : public Channel_Impl_12
       void send_client_hello(Handshake_State& state,
                              bool force_full_renegotiation,
                              Protocol_Version version,
+                             std::optional<Session> session = std::nullopt,
                              const std::vector<std::string>& next_protocols = {});
 
       void process_handshake_msg(const Handshake_State* active_state,
diff --git a/src/lib/tls/tls12/tls_record.cpp b/src/lib/tls/tls12/tls_record.cpp
index 3901c006c..88ae1b930 100644
--- a/src/lib/tls/tls12/tls_record.cpp
+++ b/src/lib/tls/tls12/tls_record.cpp
@@ -75,7 +75,7 @@ Connection_Cipher_State::Connection_Cipher_State(Protocol_Version version,
       }
    else
       {
-      m_aead = AEAD_Mode::create_or_throw(suite.cipher_algo(), our_side ? Cipher_Dir::Encryption : Cipher_Dir::Decryption);
+      m_aead = AEAD_Mode::create_or_throw(suite.cipher_algo(), our_side ? ENCRYPTION : DECRYPTION);
       }
 
    m_aead->set_key(aead_key);
diff --git a/src/lib/tls/tls12/tls_server_impl_12.cpp b/src/lib/tls/tls12/tls_server_impl_12.cpp
index c90815bce..553d85c81 100644
--- a/src/lib/tls/tls12/tls_server_impl_12.cpp
+++ b/src/lib/tls/tls12/tls_server_impl_12.cpp
@@ -505,7 +505,7 @@ void Server_Impl_12::process_client_hello_msg(const Handshake_State* active_stat
                        m_creds,
                        callbacks(),
                        pending_state.client_hello(),
-                       std::chrono::seconds(policy().session_ticket_lifetime()));
+                       policy().session_ticket_lifetime());
 
    bool have_session_ticket_key = false;
 
diff --git a/src/lib/tls/tls13/tls_channel_impl_13.cpp b/src/lib/tls/tls13/tls_channel_impl_13.cpp
index 2327022fa..13bb0e6b7 100644
--- a/src/lib/tls/tls13/tls_channel_impl_13.cpp
+++ b/src/lib/tls/tls13/tls_channel_impl_13.cpp
@@ -432,13 +432,16 @@ void Channel_Impl_13::shutdown()
    m_cipher_state.reset();
    }
 
-void Channel_Impl_13::expect_downgrade(const Server_Information& server_info)
+void Channel_Impl_13::expect_downgrade(const Server_Information&       server_info,
+                                       const std::vector<std::string>& next_protocols)
    {
    Downgrade_Information di
       {
          {},
          {},
+         {},
       server_info,
+      next_protocols,
       Botan::TLS::Channel::IO_BUF_DEFAULT_SIZE,
       callbacks(),
       session_manager(),
diff --git a/src/lib/tls/tls13/tls_channel_impl_13.h b/src/lib/tls/tls13/tls_channel_impl_13.h
index c6141824f..90a5ebd1b 100644
--- a/src/lib/tls/tls13/tls_channel_impl_13.h
+++ b/src/lib/tls/tls13/tls_channel_impl_13.h
@@ -248,7 +248,7 @@ class Channel_Impl_13 : public Channel_Impl
        * can be preserved.
        * @sa `Channel_Impl::Downgrade_Information`
        */
-      void expect_downgrade(const Server_Information& server_info);
+      void expect_downgrade(const Server_Information& server_info, const std::vector<std::string>& next_protocols);
 
       /**
        * Set the record size limits as negotiated by the "record_size_limit"
diff --git a/src/lib/tls/tls13/tls_cipher_state.cpp b/src/lib/tls/tls13/tls_cipher_state.cpp
index ea9104412..f63d27e13 100644
--- a/src/lib/tls/tls13/tls_cipher_state.cpp
+++ b/src/lib/tls/tls13/tls_cipher_state.cpp
@@ -474,8 +474,8 @@ void Cipher_State::advance_with_server_hello(const Ciphersuite& cipher,
    BOTAN_ASSERT_NOMSG(!m_decrypt);
    BOTAN_STATE_CHECK(is_compatible_with(cipher));
 
-   m_encrypt = AEAD_Mode::create_or_throw(cipher.cipher_algo(), Cipher_Dir::Encryption);
-   m_decrypt = AEAD_Mode::create_or_throw(cipher.cipher_algo(), Cipher_Dir::Decryption);
+   m_encrypt = AEAD_Mode::create_or_throw(cipher.cipher_algo(), ENCRYPTION);
+   m_decrypt = AEAD_Mode::create_or_throw(cipher.cipher_algo(), DECRYPTION);
 
    const auto handshake_secret = hkdf_extract(std::move(shared_secret));
 
diff --git a/src/lib/tls/tls13/tls_client_impl_13.cpp b/src/lib/tls/tls13/tls_client_impl_13.cpp
index 7e196b8d7..83ad8266a 100644
--- a/src/lib/tls/tls13/tls_client_impl_13.cpp
+++ b/src/lib/tls/tls13/tls_client_impl_13.cpp
@@ -31,14 +31,28 @@ Client_Impl_13::Client_Impl_13(Callbacks& callbacks,
                                const std::vector<std::string>& next_protocols) :
    Channel_Impl_13(callbacks, session_manager, creds, rng, policy, false /* is_server */),
    m_info(info),
-   m_should_send_ccs(false),
-   m_resumed_session(find_session_for_resumption())
+   m_should_send_ccs(false)
    {
 #if defined(BOTAN_HAS_TLS_12)
    if(policy.allow_tls12())
-      { expect_downgrade(info); }
+      { expect_downgrade(info, next_protocols); }
 #endif
 
+   if(auto session_to_resume = find_session_for_resumption())
+      {
+      if(!session_to_resume->version().is_pre_tls_13())
+         {
+         m_resumed_session = std::move(session_to_resume);
+         }
+      else if(expects_downgrade())
+         {
+         // If we found a session that was created with TLS 1.2, we downgrade
+         // the implementation right away, before even issuing a Client Hello.
+         request_downgrade_for_resumption(std::move(session_to_resume.value()));
+         return;
+         }
+      }
+
    auto msg = send_handshake_message(m_handshake_state.sending(Client_Hello_13(
                              policy,
                              callbacks,
@@ -120,14 +134,14 @@ std::optional<Session> Client_Impl_13::find_session_for_resumption()
    if(!session_manager().load_from_server_info(m_info, session))
       return std::nullopt;
 
-   // Ignore sessions that were not negotiated as TLS 1.3
-   if(session.version().is_pre_tls_13())
-      return std::nullopt;
 
    // RFC 8446 4.2.11.1
    //    Clients MUST NOT attempt to use tickets which have ages greater than
    //    the "ticket_lifetime" value which was provided with the ticket.
-   const auto session_age = callbacks().tls_current_timestamp() - session.start_time();
+   const auto session_age =
+      std::chrono::duration_cast<std::chrono::seconds>(
+         callbacks().tls_current_timestamp() - session.start_time());
+
    if(session_age > session.lifetime_hint())
       {
       session_manager().remove_entry(session.session_id());
diff --git a/src/lib/tls/tls13/tls_server_impl_13.cpp b/src/lib/tls/tls13/tls_server_impl_13.cpp
index 5a1f7bc69..0f91864a5 100644
--- a/src/lib/tls/tls13/tls_server_impl_13.cpp
+++ b/src/lib/tls/tls13/tls_server_impl_13.cpp
@@ -25,7 +25,7 @@ Server_Impl_13::Server_Impl_13(Callbacks& callbacks,
    {
 #if defined(BOTAN_HAS_TLS_12)
    if(policy.allow_tls12())
-      { expect_downgrade({}); }
+      { expect_downgrade({}, {}); }
 #endif
 
    m_transitions.set_expected_next(Handshake_Type::ClientHello);
diff --git a/src/lib/tls/tls_channel_impl.h b/src/lib/tls/tls_channel_impl.h
index c2e8500cf..4c5ee926c 100644
--- a/src/lib/tls/tls_channel_impl.h
+++ b/src/lib/tls/tls_channel_impl.h
@@ -160,7 +160,12 @@ class Channel_Impl
          /// The full data transcript received from the peer. This will contain the server hello message that forced us to downgrade.
          std::vector<uint8_t> peer_transcript;
 
+         /// The TLS 1.2 session information found by a TLS 1.3 client that
+         /// caused it to initiate a downgrade before even sending a client hello.
+         std::optional<Session> tls12_session;
+
          Server_Information server_info;
+         std::vector<std::string> next_protocols;
          size_t io_buffer_size;
 
          Callbacks& callbacks;
@@ -210,6 +215,16 @@ class Channel_Impl
          m_downgrade_info->will_downgrade = true;
          }
 
+      void request_downgrade_for_resumption(Session session)
+         {
+         BOTAN_STATE_CHECK(m_downgrade_info &&
+                           m_downgrade_info->client_hello_message.empty() &&
+                           m_downgrade_info->peer_transcript.empty() &&
+                           !m_downgrade_info->tls12_session.has_value());
+         m_downgrade_info->tls12_session = std::move(session);
+         request_downgrade();
+         }
+
    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 711ab23ea..db8a3dcfe 100644
--- a/src/lib/tls/tls_client.cpp
+++ b/src/lib/tls/tls_client.cpp
@@ -36,21 +36,12 @@ Client::Client(Callbacks& callbacks,
                const std::vector<std::string>& next_protocols,
                size_t io_buf_sz)
    {
-   Protocol_Version effective_version = offer_version;
    BOTAN_ARG_CHECK(policy.acceptable_protocol_version(offer_version),
                    "Policy does not allow to offer requested protocol version");
 
 #if defined(BOTAN_HAS_TLS_13)
-   // downgrade to TLS 1.2 directly if we have a legacy session to resume
-   if(effective_version == Protocol_Version::TLS_V13)
-      {
-      Session session;
-      const bool found = session_manager.load_from_server_info(info, session);
-      if(found && session.version().is_pre_tls_13())
-         effective_version = session.version();
-      }
 
-   if(effective_version == Protocol_Version::TLS_V13)
+   if(offer_version == Protocol_Version::TLS_V13)
       {
       m_impl = std::make_unique<Client_Impl_13>(
                   callbacks, session_manager, creds, policy,
@@ -58,28 +49,51 @@ Client::Client(Callbacks& callbacks,
 
       if(m_impl->expects_downgrade())
          { m_impl->set_io_buffer_size(io_buf_sz); }
+
+      if(m_impl->is_downgrading())
+         {
+         // TLS 1.3 implementation found a resumable TLS 1.2 session and
+         // requested a downgrade right away.
+         downgrade();
+         }
       }
    else
 #endif
       m_impl = std::make_unique<Client_Impl_12>(
                   callbacks, session_manager, creds, policy,
-                  rng, info, effective_version.is_datagram_protocol(),
+                  rng, info, offer_version.is_datagram_protocol(),
                   next_protocols, io_buf_sz);
    }
 
 Client::~Client() = default;
 
+size_t Client::downgrade()
+   {
+   BOTAN_ASSERT_NOMSG(m_impl->is_downgrading());
+
+   auto info = m_impl->extract_downgrade_info();
+   m_impl = std::make_unique<Client_Impl_12>(*info);
+
+   if(!info->peer_transcript.empty())
+      {
+      // replay peer data received so far
+      return m_impl->received_data(info->peer_transcript.data(), info->peer_transcript.size());
+      }
+   else
+      {
+      // the downgrade happened due to a resumable TLS 1.2 session
+      // before any data was transferred
+      return 0;
+      }
+   }
+
 size_t Client::received_data(const uint8_t buf[], size_t buf_size)
    {
    auto read = m_impl->received_data(buf, buf_size);
 
    if(m_impl->is_downgrading())
       {
-      auto info = m_impl->extract_downgrade_info();
-      m_impl = std::make_unique<Client_Impl_12>(*info);
-
-      // replay peer data received so far
-      read = m_impl->received_data(info->peer_transcript.data(), info->peer_transcript.size());
+      read = downgrade();
       }
 
    return read;
diff --git a/src/lib/tls/tls_client.h b/src/lib/tls/tls_client.h
index fd07e928b..3bb71b9a7 100644
--- a/src/lib/tls/tls_client.h
+++ b/src/lib/tls/tls_client.h
@@ -111,6 +111,9 @@ class BOTAN_PUBLIC_API(2,0) Client final : public Channel
 
       bool timeout_check() override;
 
+   private:
+      size_t downgrade();
+
    private:
       std::unique_ptr<Channel_Impl> m_impl;
    };
diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp
index 59413d9a7..c50d074b9 100644
--- a/src/lib/tls/tls_extensions.cpp
+++ b/src/lib/tls/tls_extensions.cpp
@@ -68,7 +68,7 @@ std::unique_ptr<Extension> make_extension(TLS_Data_Reader& reader,
          return std::make_unique<Encrypt_then_MAC>(reader, size);
 
       case Extension_Code::SessionTicket:
-         return std::make_unique<Session_Ticket>(reader, size);
+         return std::make_unique<Session_Ticket_Extension>(reader, size);
 
       case Extension_Code::SupportedVersions:
          return std::make_unique<Supported_Versions>(reader, size, from);
@@ -571,8 +571,9 @@ Signature_Algorithms_Cert::Signature_Algorithms_Cert(TLS_Data_Reader& reader,
    : m_schemes(parse_signature_algorithms(reader, extension_size))
    {}
 
-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))
+Session_Ticket_Extension::Session_Ticket_Extension(TLS_Data_Reader& reader,
+                                                   uint16_t extension_size)
+   : m_ticket(reader.get_elem<uint8_t, std::vector<uint8_t>>(extension_size))
    {}
 
 SRTP_Protection_Profiles::SRTP_Protection_Profiles(TLS_Data_Reader& reader,
diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h
index 9688da29e..39bf7e728 100644
--- a/src/lib/tls/tls_extensions.h
+++ b/src/lib/tls/tls_extensions.h
@@ -211,7 +211,7 @@ class BOTAN_UNSTABLE_API Application_Layer_Protocol_Notification final : public
 /**
 * Session Ticket Extension (RFC 5077)
 */
-class BOTAN_UNSTABLE_API Session_Ticket final : public Extension
+class BOTAN_UNSTABLE_API Session_Ticket_Extension final : public Extension
    {
    public:
       static Extension_Code static_type()
@@ -227,18 +227,18 @@ class BOTAN_UNSTABLE_API Session_Ticket final : public Extension
       /**
       * Create empty extension, used by both client and server
       */
-      Session_Ticket() = default;
+      Session_Ticket_Extension() = default;
 
       /**
       * Extension with ticket, used by client
       */
-      explicit Session_Ticket(const std::vector<uint8_t>& session_ticket) :
+      explicit Session_Ticket_Extension(const std::vector<uint8_t>& session_ticket) :
          m_ticket(session_ticket) {}
 
       /**
       * Deserialize a session ticket
       */
-      Session_Ticket(TLS_Data_Reader& reader, uint16_t extension_size);
+      Session_Ticket_Extension(TLS_Data_Reader& reader, uint16_t extension_size);
 
       std::vector<uint8_t> serialize(Connection_Side) const override { return m_ticket; }
 
diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h
index 0309bbba8..4e1909e7b 100644
--- a/src/lib/tls/tls_messages.h
+++ b/src/lib/tls/tls_messages.h
@@ -15,6 +15,7 @@
 #include <memory>
 #include <optional>
 #include <variant>
+#include <chrono>
 
 #include <botan/tls_extensions.h>
 #include <botan/tls_handshake_msg.h>
@@ -905,13 +906,13 @@ class BOTAN_UNSTABLE_API New_Session_Ticket_12 final : public Handshake_Message
    public:
       Handshake_Type type() const override { return Handshake_Type::NewSessionTicket; }
 
-      uint32_t ticket_lifetime_hint() const { return m_ticket_lifetime_hint; }
+      std::chrono::seconds ticket_lifetime_hint() const { return m_ticket_lifetime_hint; }
       const std::vector<uint8_t>& ticket() const { return m_ticket; }
 
       New_Session_Ticket_12(Handshake_IO& io,
                             Handshake_Hash& hash,
                             const std::vector<uint8_t>& ticket,
-                            uint32_t lifetime);
+                            std::chrono::seconds lifetime);
 
       New_Session_Ticket_12(Handshake_IO& io,
                             Handshake_Hash& hash);
@@ -921,7 +922,7 @@ class BOTAN_UNSTABLE_API New_Session_Ticket_12 final : public Handshake_Message
       std::vector<uint8_t> serialize() const override;
 
    private:
-      uint32_t m_ticket_lifetime_hint = 0;
+      std::chrono::seconds m_ticket_lifetime_hint;
       std::vector<uint8_t> m_ticket;
    };
 
@@ -942,7 +943,7 @@ class BOTAN_UNSTABLE_API New_Session_Ticket_13 final : public Handshake_Message
       const std::vector<uint8_t>& ticket() const { return m_ticket; }
       const std::vector<uint8_t>& nonce() const { return m_ticket_nonce; }
       uint32_t ticket_age_add() const { return m_ticket_age_add; }
-      uint32_t lifetime_hint() const { return m_ticket_lifetime_hint; }
+      std::chrono::seconds lifetime_hint() const { return m_ticket_lifetime_hint; }
 
       /**
        * @return  the number of bytes allowed for early data or std::nullopt
@@ -958,7 +959,7 @@ class BOTAN_UNSTABLE_API New_Session_Ticket_13 final : public Handshake_Message
       //    of time than what is stated in the ticket_lifetime.
       //
       // ... hence we call it 'lifetime hint'.
-      uint32_t m_ticket_lifetime_hint;
+      std::chrono::seconds m_ticket_lifetime_hint;
       uint32_t m_ticket_age_add;
       std::vector<uint8_t> m_ticket_nonce;
       std::vector<uint8_t> m_ticket;
diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp
index 0282b0978..7f295ab4c 100644
--- a/src/lib/tls/tls_policy.cpp
+++ b/src/lib/tls/tls_policy.cpp
@@ -275,9 +275,9 @@ void Policy::check_peer_key_acceptable(const Public_Key& public_key) const
                            std::to_string(expected_keylength));
    }
 
-uint32_t Policy::session_ticket_lifetime() const
+std::chrono::seconds Policy::session_ticket_lifetime() const
    {
-   return 86400; // ~1 day
+   return std::chrono::days(1);
    }
 
 bool Policy::acceptable_protocol_version(Protocol_Version version) const
@@ -586,7 +586,7 @@ void Policy::print(std::ostream& o) const
    if (record_size_limit().has_value()) {
       o << "record_size_limit = " << record_size_limit().value() << '\n';
    }
-   o << "session_ticket_lifetime = " << session_ticket_lifetime() << '\n';
+   o << "session_ticket_lifetime = " << session_ticket_lifetime().count() << '\n';
    o << "minimum_dh_group_size = " << minimum_dh_group_size() << '\n';
    o << "minimum_ecdh_group_size = " << minimum_ecdh_group_size() << '\n';
    o << "minimum_rsa_bits = " << minimum_rsa_bits() << '\n';
diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h
index 1a9468337..30d657cb5 100644
--- a/src/lib/tls/tls_policy.h
+++ b/src/lib/tls/tls_policy.h
@@ -253,7 +253,7 @@ class BOTAN_PUBLIC_API(2,0) Policy
       * tickets do not expire until the session ticket key rolls over.
       * Expired session tickets cannot be used to resume a session.
       */
-      virtual uint32_t session_ticket_lifetime() const;
+      virtual std::chrono::seconds session_ticket_lifetime() const;
 
       /**
       * If this returns a non-empty vector, and DTLS is negotiated,
@@ -630,7 +630,7 @@ class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy
 
       bool hide_unknown_users() const override;
 
-      uint32_t session_ticket_lifetime() const override;
+      std::chrono::seconds session_ticket_lifetime() const override;
 
       bool tls_13_middlebox_compatibility_mode() const override;
 
@@ -653,6 +653,8 @@ class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy
 
       size_t get_len(const std::string& key, size_t def) const;
 
+      std::chrono::seconds get_duration(const std::string& key, std::chrono::seconds def) const;
+
       bool get_bool(const std::string& key, bool def) const;
 
       std::string get_str(const std::string& key, const std::string& def = "") const;
diff --git a/src/lib/tls/tls_session.cpp b/src/lib/tls/tls_session.cpp
index b26dac97b..7d28e75d3 100644
--- a/src/lib/tls/tls_session.cpp
+++ b/src/lib/tls/tls_session.cpp
@@ -45,7 +45,8 @@ Session::Session(const std::vector<uint8_t>& session_identifier,
                  const std::vector<uint8_t>& ticket,
                  const Server_Information& server_info,
                  uint16_t srtp_profile,
-                 std::chrono::system_clock::time_point current_timestamp) :
+                 std::chrono::system_clock::time_point current_timestamp,
+                 std::chrono::seconds lifetime_hint) :
    m_start_time(current_timestamp),
    m_identifier(session_identifier),
    m_session_ticket(ticket),
@@ -61,7 +62,7 @@ Session::Session(const std::vector<uint8_t>& session_identifier,
    m_early_data_allowed(false),
    m_max_early_data_bytes(0),
    m_ticket_age_add(0),
-   m_lifetime_hint(0)
+   m_lifetime_hint(lifetime_hint)
    {
    BOTAN_ARG_CHECK(version.is_pre_tls_13(),
                    "Instantiated a TLS 1.2 session object with a TLS version newer than 1.2");
@@ -73,7 +74,7 @@ Session::Session(const std::vector<uint8_t>& session_ticket,
                  const secure_vector<uint8_t>& session_psk,
                  const std::optional<uint32_t>& max_early_data_bytes,
                  uint32_t ticket_age_add,
-                 uint32_t lifetime_hint,
+                 std::chrono::seconds lifetime_hint,
                  Protocol_Version version,
                  uint16_t ciphersuite,
                  Connection_Side side,
@@ -135,13 +136,9 @@ Session::Session(secure_vector<uint8_t>&& session_psk,
    m_server_info(client_hello.sni_hostname()),
    m_early_data_allowed(max_early_data_bytes.has_value()),
    m_max_early_data_bytes(max_early_data_bytes.value_or(0)),
-   m_ticket_age_add(load_be<uint32_t>(rng.random_vec(4).data(), 0))
-   {
-   const auto lifetime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(lifetime_hint).count();
-   BOTAN_ARG_CHECK(lifetime_ms <= std::numeric_limits<uint32_t>::max(),
-                   "lifetime is too long");
-   m_lifetime_hint = static_cast<uint32_t>(lifetime_ms);
-   }
+   m_ticket_age_add(load_be<uint32_t>(rng.random_vec(4).data(), 0)),
+   m_lifetime_hint(lifetime_hint)
+   {}
 
 #endif
 
@@ -170,6 +167,7 @@ Session::Session(const uint8_t ber[], size_t ber_len)
    size_t fragment_size = 0;
    size_t compression_method = 0;
    uint16_t ciphersuite_code = 0;
+   uint64_t lifetime_hint = 0;
 
    BER_Decoder(ber, ber_len)
       .start_sequence()
@@ -196,7 +194,7 @@ Session::Session(const uint8_t ber[], size_t ber_len)
         .decode(m_early_data_allowed)
         .decode_integer_type(m_max_early_data_bytes)
         .decode_integer_type(m_ticket_age_add)
-        .decode_integer_type(m_lifetime_hint)
+        .decode_integer_type(lifetime_hint)
       .end_cons()
       .verify_end();
 
@@ -241,6 +239,8 @@ Session::Session(const uint8_t ber[], size_t ber_len)
       while(!certs.end_of_data())
          m_peer_certs.push_back(X509_Certificate(certs));
       }
+
+   m_lifetime_hint = std::chrono::seconds(lifetime_hint);
    }
 
 secure_vector<uint8_t> Session::DER_encode() const
@@ -279,7 +279,7 @@ secure_vector<uint8_t> Session::DER_encode() const
          .encode(m_early_data_allowed)
          .encode(static_cast<size_t>(m_max_early_data_bytes))
          .encode(static_cast<size_t>(m_ticket_age_add))
-         .encode(static_cast<size_t>(m_lifetime_hint))
+         .encode(static_cast<size_t>(m_lifetime_hint.count()))
       .end_cons()
    .get_contents();
    }
@@ -370,7 +370,7 @@ Session::encrypt(const SymmetricKey& key, RandomNumberGenerator& rng) const
    buf += key_seed;
    buf += aead_nonce;
 
-   std::unique_ptr<AEAD_Mode> aead = AEAD_Mode::create_or_throw(TLS_SESSION_CRYPT_AEAD, Cipher_Dir::Encryption);
+   std::unique_ptr<AEAD_Mode> aead = AEAD_Mode::create_or_throw(TLS_SESSION_CRYPT_AEAD, ENCRYPTION);
    BOTAN_ASSERT_NOMSG(aead->valid_nonce_length(TLS_SESSION_CRYPT_AEAD_NONCE_LEN));
    BOTAN_ASSERT_NOMSG(aead->tag_size() == TLS_SESSION_CRYPT_AEAD_TAG_SIZE);
    aead->set_key(aead_key);
@@ -415,7 +415,7 @@ Session Session::decrypt(const uint8_t in[], size_t in_len, const SymmetricKey&
       hmac->update(key_seed, TLS_SESSION_CRYPT_AEAD_KEY_SEED_LEN);
       const secure_vector<uint8_t> aead_key = hmac->final();
 
-      auto aead = AEAD_Mode::create_or_throw(TLS_SESSION_CRYPT_AEAD, Cipher_Dir::Decryption);
+      auto aead = AEAD_Mode::create_or_throw(TLS_SESSION_CRYPT_AEAD, DECRYPTION);
       aead->set_key(aead_key);
       aead->set_associated_data(in, TLS_SESSION_CRYPT_HDR_LEN);
       aead->start(aead_nonce, TLS_SESSION_CRYPT_AEAD_NONCE_LEN);
diff --git a/src/lib/tls/tls_session.h b/src/lib/tls/tls_session.h
index b4b1a717d..3056fb976 100644
--- a/src/lib/tls/tls_session.h
+++ b/src/lib/tls/tls_session.h
@@ -51,7 +51,8 @@ class BOTAN_PUBLIC_API(2,0) Session final
               const std::vector<uint8_t>& session_ticket,
               const Server_Information& server_info,
               uint16_t srtp_profile,
-              std::chrono::system_clock::time_point current_timestamp);
+              std::chrono::system_clock::time_point current_timestamp,
+              std::chrono::seconds lifetime_hint = std::chrono::seconds::max());
 
 #if defined(BOTAN_HAS_TLS_13)
 
@@ -62,7 +63,7 @@ class BOTAN_PUBLIC_API(2,0) Session final
               const secure_vector<uint8_t>& session_psk,
               const std::optional<uint32_t>& max_early_data_bytes,
               uint32_t ticket_age_add,
-              uint32_t lifetime_hint,
+              std::chrono::seconds lifetime_hint,
               Protocol_Version version,
               uint16_t ciphersuite,
               Connection_Side side,
@@ -218,7 +219,7 @@ class BOTAN_PUBLIC_API(2,0) Session final
       /**
       * @return the lifetime of the ticket as defined by the TLS server
       */
-      std::chrono::seconds lifetime_hint() const { return std::chrono::seconds(m_lifetime_hint); }
+      std::chrono::seconds lifetime_hint() const { return m_lifetime_hint; }
 
    private:
       // Struct Version history
@@ -254,7 +255,7 @@ class BOTAN_PUBLIC_API(2,0) Session final
       bool m_early_data_allowed;
       uint32_t m_max_early_data_bytes;
       uint32_t m_ticket_age_add;
-      uint32_t m_lifetime_hint; // in milliseconds
+      std::chrono::seconds m_lifetime_hint;
    };
 
 }
diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp
index 41114542f..ec0a284f5 100644
--- a/src/lib/tls/tls_text_policy.cpp
+++ b/src/lib/tls/tls_text_policy.cpp
@@ -196,9 +196,9 @@ bool Text_Policy::hide_unknown_users() const
    return get_bool("hide_unknown_users", Policy::hide_unknown_users());
    }
 
-uint32_t Text_Policy::session_ticket_lifetime() const
+std::chrono::seconds Text_Policy::session_ticket_lifetime() const
    {
-   return static_cast<uint32_t>(get_len("session_ticket_lifetime", Policy::session_ticket_lifetime()));
+   return get_duration("session_ticket_lifetime", Policy::session_ticket_lifetime());
    }
 
 std::vector<uint16_t> Text_Policy::srtp_profiles() const
@@ -303,6 +303,26 @@ size_t Text_Policy::get_len(const std::string& key, size_t def) const
    return to_u32bit(v);
    }
 
+std::chrono::seconds Text_Policy::get_duration(const std::string& key, std::chrono::seconds def) const
+   {
+   using rep_t = std::chrono::seconds::rep;
+   constexpr rep_t max_seconds = std::chrono::seconds::max().count();
+   constexpr auto max_sizet = std::numeric_limits<size_t>::max();
+   using ull = unsigned long long;
+
+   // The concrete type of `rep` is not specified exactly. Let's play it extra safe...
+   // e.g. on 32-bit platforms size_t is 32 bits but rep_t is "at least 35 bits"
+
+   // at least zero and certainly fitting into rep_t
+   const rep_t positive_default = std::max(def.count(), rep_t(0));
+   // at least zero but capped to whatever size_t can handle
+   const size_t positive_capped_default = static_cast<size_t>(std::min<ull>(positive_default, max_sizet));
+   // at least zero but capped to whatever rep_t can handle
+   const rep_t result = static_cast<rep_t>(std::min<ull>(get_len(key, positive_capped_default), max_seconds));
+
+   return std::chrono::seconds(result);
+   }
+
 bool Text_Policy::get_bool(const std::string& key, bool def) const
    {
    const std::string v = get_str(key);


Test patch:
diff --git a/src/cli/timing_tests.cpp b/src/cli/timing_tests.cpp
index 723f94677..1125a6743 100644
--- a/src/cli/timing_tests.cpp
+++ b/src/cli/timing_tests.cpp
@@ -226,7 +226,7 @@ std::vector<uint8_t> Lucky13_Timing_Test::prepare_input(const std::string& input
    const std::vector<uint8_t> key(16);
    const std::vector<uint8_t> iv(16);
 
-   auto enc = Botan::Cipher_Mode::create("AES-128/CBC/NoPadding", Botan::Cipher_Dir::Encryption);
+   std::unique_ptr<Botan::Cipher_Mode> enc(Botan::Cipher_Mode::create("AES-128/CBC/NoPadding", Botan::ENCRYPTION));
    enc->set_key(key);
    enc->start(iv);
    Botan::secure_vector<uint8_t> buf(input_vector.begin(), input_vector.end());
diff --git a/src/tests/test_aead.cpp b/src/tests/test_aead.cpp
index 688dcaef9..48fa8d660 100644
--- a/src/tests/test_aead.cpp
+++ b/src/tests/test_aead.cpp
@@ -33,7 +33,7 @@ class AEAD_Tests final : public Text_Based_Test
 
          Test::Result result(algo);
 
-         std::unique_ptr<Botan::AEAD_Mode> enc(Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption));
+         std::unique_ptr<Botan::AEAD_Mode> enc(Botan::AEAD_Mode::create(algo, Botan::ENCRYPTION));
 
          result.test_eq("AEAD encrypt output_length is correct", enc->output_length(input.size()), expected.size());
 
@@ -204,7 +204,7 @@ class AEAD_Tests final : public Text_Based_Test
 
          Test::Result result(algo);
 
-         std::unique_ptr<Botan::AEAD_Mode> dec(Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption));
+         std::unique_ptr<Botan::AEAD_Mode> dec(Botan::AEAD_Mode::create(algo, Botan::DECRYPTION));
 
          result.test_eq("AEAD decrypt output_length is correct", dec->output_length(input.size()), expected.size());
 
@@ -439,8 +439,8 @@ class AEAD_Tests final : public Text_Based_Test
 
          Test::Result result(algo);
 
-         std::unique_ptr<Botan::AEAD_Mode> enc(Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Encryption));
-         std::unique_ptr<Botan::AEAD_Mode> dec(Botan::AEAD_Mode::create(algo, Botan::Cipher_Dir::Decryption));
+         std::unique_ptr<Botan::AEAD_Mode> enc(Botan::AEAD_Mode::create(algo, Botan::ENCRYPTION));
+         std::unique_ptr<Botan::AEAD_Mode> dec(Botan::AEAD_Mode::create(algo, Botan::DECRYPTION));
 
          if(!enc || !dec)
             {
diff --git a/src/tests/test_dlies.cpp b/src/tests/test_dlies.cpp
index 641eb9fa2..653436c35 100644
--- a/src/tests/test_dlies.cpp
+++ b/src/tests/test_dlies.cpp
@@ -64,8 +64,8 @@ class DLIES_KAT_Tests final : public Text_Based_Test
 
          if(cipher_algo != "XOR")
             {
-            enc = Botan::Cipher_Mode::create(cipher_algo, Botan::Cipher_Dir::Encryption);
-            dec = Botan::Cipher_Mode::create(cipher_algo, Botan::Cipher_Dir::Decryption);
+            enc = Botan::Cipher_Mode::create(cipher_algo, Botan::ENCRYPTION);
+            dec = Botan::Cipher_Mode::create(cipher_algo, Botan::DECRYPTION);
 
             if(!enc || !dec)
                {
diff --git a/src/tests/test_filters.cpp b/src/tests/test_filters.cpp
index 23be31c2c..5709b5cf2 100644
--- a/src/tests/test_filters.cpp
+++ b/src/tests/test_filters.cpp
@@ -383,10 +383,10 @@ class Filter_Tests final : public Test
             "7195FFE735B0A95065BA244C77A11F",
          };
 
-         Botan::Keyed_Filter* cfb_enc = Botan::get_cipher("Blowfish/CFB", key, iv, Botan::Cipher_Dir::Encryption);
+         Botan::Keyed_Filter* cfb_enc = Botan::get_cipher("Blowfish/CFB", key, iv, Botan::ENCRYPTION);
          Botan::Pipe enc_pipe(cfb_enc, new Botan::Hex_Encoder);
 
-         Botan::Keyed_Filter* cfb_dec = Botan::get_cipher("Blowfish/CFB", key, Botan::Cipher_Dir::Decryption);
+         Botan::Keyed_Filter* cfb_dec = Botan::get_cipher("Blowfish/CFB", key, Botan::DECRYPTION);
          cfb_dec->set_iv(iv);
          Botan::Pipe dec_pipe(new Botan::Hex_Decoder, cfb_dec, new Botan::Hex_Encoder);
 
@@ -423,7 +423,7 @@ class Filter_Tests final : public Test
 
 #if defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_MODE_CBC) && defined(BOTAN_HAS_CIPHER_MODE_PADDING)
          Botan::Cipher_Mode_Filter* cipher =
-            new Botan::Cipher_Mode_Filter(Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Encryption));
+            new Botan::Cipher_Mode_Filter(Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::ENCRYPTION));
 
          result.test_eq("Cipher filter name", cipher->name(), "AES-128/CBC/PKCS7");
 
@@ -458,7 +458,7 @@ class Filter_Tests final : public Test
          result.test_eq("Ciphertext3", ciphertext3, "1241B9976F73051BCF809525D6E86C25");
 
          Botan::Cipher_Mode_Filter* dec_cipher =
-            new Botan::Cipher_Mode_Filter(Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Decryption));
+            new Botan::Cipher_Mode_Filter(Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::DECRYPTION));
          pipe.append(dec_cipher);
          dec_cipher->set_key(Botan::SymmetricKey("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
          dec_cipher->set_iv(Botan::InitializationVector("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB"));
diff --git a/src/tests/test_modes.cpp b/src/tests/test_modes.cpp
index fd476bc2d..fae96d251 100644
--- a/src/tests/test_modes.cpp
+++ b/src/tests/test_modes.cpp
@@ -47,9 +47,9 @@ class Cipher_Mode_Tests final : public Text_Based_Test
          for(auto&& provider_ask : providers)
             {
             std::unique_ptr<Botan::Cipher_Mode> enc(Botan::Cipher_Mode::create(
-                  algo, Botan::Cipher_Dir::Encryption, provider_ask));
+                  algo, Botan::ENCRYPTION, provider_ask));
             std::unique_ptr<Botan::Cipher_Mode> dec(Botan::Cipher_Mode::create(
-                  algo, Botan::Cipher_Dir::Decryption, provider_ask));
+                  algo, Botan::DECRYPTION, provider_ask));
 
             if(!enc || !dec)
                {
@@ -285,9 +285,9 @@ class Cipher_Mode_IV_Carry_Tests final : public Test
 
 #if defined(BOTAN_HAS_MODE_CBC) && defined(BOTAN_HAS_AES)
          std::unique_ptr<Botan::Cipher_Mode> enc(
-            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Encryption));
+            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::ENCRYPTION));
          std::unique_ptr<Botan::Cipher_Mode> dec(
-            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::Cipher_Dir::Decryption));
+            Botan::Cipher_Mode::create("AES-128/CBC/PKCS7", Botan::DECRYPTION));
 
          const std::vector<uint8_t> key(16, 0xAA);
          const std::vector<uint8_t> iv(16, 0xAA);
@@ -338,9 +338,9 @@ class Cipher_Mode_IV_Carry_Tests final : public Test
          Test::Result result("CFB IV carry");
 #if defined(BOTAN_HAS_MODE_CFB) && defined(BOTAN_HAS_AES)
          std::unique_ptr<Botan::Cipher_Mode> enc(
-            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::Cipher_Dir::Encryption));
+            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::ENCRYPTION));
          std::unique_ptr<Botan::Cipher_Mode> dec(
-            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::Cipher_Dir::Decryption));
+            Botan::Cipher_Mode::create("AES-128/CFB(8)", Botan::DECRYPTION));
 
          const std::vector<uint8_t> key(16, 0xAA);
          const std::vector<uint8_t> iv(16, 0xAB);
@@ -387,9 +387,9 @@ class Cipher_Mode_IV_Carry_Tests final : public Test
 #if defined(BOTAN_HAS_CTR_BE) && defined(BOTAN_HAS_AES)
 
          std::unique_ptr<Botan::Cipher_Mode> enc(
-            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::Cipher_Dir::Encryption));
+            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::ENCRYPTION));
          std::unique_ptr<Botan::Cipher_Mode> dec(
-            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::Cipher_Dir::Decryption));
+            Botan::Cipher_Mode::create("AES-128/CTR-BE", Botan::DECRYPTION));
 
          const std::vector<uint8_t> key =
             Botan::hex_decode("2B7E151628AED2A6ABF7158809CF4F3C");
diff --git a/src/tests/test_siv.cpp b/src/tests/test_siv.cpp
index c838be555..4b11c8bb9 100644
--- a/src/tests/test_siv.cpp
+++ b/src/tests/test_siv.cpp
@@ -35,7 +35,7 @@ class SIV_Tests final : public Text_Based_Test
 
          Test::Result result(siv_name);
 
-         auto siv = Botan::AEAD_Mode::create(siv_name, Botan::Cipher_Dir::Encryption);
+         auto siv = Botan::AEAD_Mode::create(siv_name, Botan::ENCRYPTION);
 
          if(!siv)
             {
diff --git a/src/tests/test_tls_cipher_state.cpp b/src/tests/test_tls_cipher_state.cpp
index ad950f7ee..0ba84e72d 100644
--- a/src/tests/test_tls_cipher_state.cpp
+++ b/src/tests/test_tls_cipher_state.cpp
@@ -20,19 +20,6 @@ using Test = Botan_Tests::Test;
 using namespace Botan;
 using namespace Botan::TLS;
 
-std::vector<Test::Result> flatten(std::vector<std::vector<Test::Result>> result_lists)
-   {
-   std::vector<Test::Result> results;
-   for(auto& result_list : result_lists)
-      {
-      for(auto& result : result_list)
-         {
-         results.emplace_back(std::move(result));
-         }
-      }
-   return results;
-   }
-
 decltype(auto) make_CHECK_both(Cipher_State* cs_client, Cipher_State* cs_server)
    {
    using namespace std::placeholders;
@@ -307,7 +294,7 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt1()
 
    auto CHECK_both = make_CHECK_both(cs_client.get(), cs_server.get());
 
-   return flatten(
+   return Test::flatten_result_lists(
       {
       CHECK_both("ciphersuite compatibility", [&](Cipher_State* cs, Connection_Side side, Test::Result& result)
          {
@@ -589,7 +576,7 @@ std::vector<Test::Result> test_secret_derivation_rfc8448_rtt0()
 
    auto CHECK_both = make_CHECK_both(cs_client.get(), cs_server.get());
 
-   return flatten(
+   return Test::flatten_result_lists(
       {
       CHECK_both("calculating PSK binder", [&] (Cipher_State* cs, Connection_Side, Test::Result& result)
          {
diff --git a/src/tests/test_tls_rfc8448.cpp b/src/tests/test_tls_rfc8448.cpp
index 98347d7f7..5bdd0f674 100644
--- a/src/tests/test_tls_rfc8448.cpp
+++ b/src/tests/test_tls_rfc8448.cpp
@@ -997,7 +997,7 @@ class Test_TLS_RFC8448_Client : public Test_TLS_RFC8448
                // For some reason, presumably checking compatibility, the RFC 8448 Client
                // Hello includes a (TLS 1.2) Session_Ticket extension. We don't normally add
                // this obsoleted extension in a TLS 1.3 client.
-               exts.add(new Botan::TLS::Session_Ticket());
+               exts.add(new Botan::TLS::Session_Ticket_Extension());
 
                add_renegotiation_extension(exts);
                sort_client_extensions(exts, side);
diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp
index 5a158b4ba..6fc432d1a 100644
--- a/src/tests/tests.cpp
+++ b/src/tests/tests.cpp
@@ -28,6 +28,13 @@
    #include <unistd.h>
 #endif
 
+#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
+  #include <version>
+  #if defined(__cpp_lib_filesystem)
+    #include <filesystem>
+  #endif
+#endif
+
 namespace Botan_Tests {
 
 void Test::Result::merge(const Result& other, bool ignore_test_name)
@@ -637,6 +644,20 @@ uint64_t Test::timestamp()
    return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
    }
 
+//static
+std::vector<Test::Result> Test::flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists)
+   {
+   std::vector<Test::Result> results;
+   for(auto& result_list : result_lists)
+      {
+      for(auto& result : result_list)
+         {
+         results.emplace_back(std::move(result));
+         }
+      }
+   return results;
+   }
+
 //static
 std::set<std::string> Test::registered_tests()
    {
@@ -696,6 +717,19 @@ std::string Test::temp_file_name(const std::string& basename)
 #endif
    }
 
+bool Test::copy_file(const std::string& from, const std::string& to)
+   {
+#if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
+   std::error_code ec; // don't throw, just return false on error
+   return std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing, ec);
+#else
+   // TODO: implement fallbacks to POSIX or WIN32
+   // ... but then again: it's 2023 and we're using C++20 :o)
+   BOTAN_UNUSED(from, to);
+   throw Botan::No_Filesystem_Access();
+#endif
+   }
+
 std::string Test::read_data_file(const std::string& path)
    {
    const std::string fsname = Test::data_file(path);
@@ -760,6 +794,19 @@ std::string Test::data_file(const std::string& what)
    return Test::data_dir() + "/" + what;
    }
 
+//static
+std::string Test::data_file_as_temporary_copy(const std::string &what)
+   {
+   auto tmp_basename = what;
+   std::replace(tmp_basename.begin(), tmp_basename.end(), '/', '_');
+   const auto temp_file = temp_file_name("tmp-" + tmp_basename);
+   if(temp_file.empty())
+      { return ""; }
+   if(!Test::copy_file(data_file(what), temp_file))
+      { return ""; }
+   return temp_file;
+   }
+
 //static
 std::vector<std::string> Test::provider_filter(const std::vector<std::string>& in)
    {
diff --git a/src/tests/tests.h b/src/tests/tests.h
index 4ed5967f3..910f26da9 100644
--- a/src/tests/tests.h
+++ b/src/tests/tests.h
@@ -657,6 +657,7 @@ class Test
       static bool test_needs_serialization(const std::string& test_name);
 
       static std::string data_file(const std::string& what);
+      static std::string data_file_as_temporary_copy(const std::string &what);
 
       static std::string format_time(uint64_t nanoseconds);
       static std::string format_time(const std::chrono::nanoseconds nanoseconds)
@@ -704,6 +705,7 @@ class Test
       static const std::string& pkcs11_lib() { return options().pkcs11_lib(); }
 
       static std::string temp_file_name(const std::string& basename);
+      static bool copy_file(const std::string& from, const std::string& to);
 
       static std::vector<std::string> provider_filter(const std::vector<std::string>& providers);
 
@@ -714,6 +716,8 @@ class Test
       static std::string random_password();
       static uint64_t timestamp(); // nanoseconds arbitrary epoch
 
+      static std::vector<Test::Result> flatten_result_lists(std::vector<std::vector<Test::Result>> result_lists);
+
    private:
       static Test_Options m_opts;
       static std::unique_ptr<Botan::RandomNumberGenerator> m_test_rng;