Repository: randombit/botan
Pull request #1954 (2019-05-20 19:21:15)

Add BoGo test shim
This adds a shim for testing against BoGo (https://boringssl.googlesource.com/boringssl/+/refs/heads/master/ssl/test/) and fixes a number of minor issues detected by it. Currently the shim doesn't have support for testing DTLS.

This really needs to be part of CI, which I will work on in a later PR.


Patch:
diff --git a/configure.py b/configure.py
index 46d119778..7100b013d 100755
--- a/configure.py
+++ b/configure.py
@@ -481,6 +481,9 @@ def process_command_line(args): # pylint: disable=too-many-locals,too-many-state
     build_group.add_option('--with-debug-asserts', action='store_true', default=False,
                            help=optparse.SUPPRESS_HELP)
 
+    build_group.add_option('--build-bogo-shim', action='store_true', default=False,
+                           help=optparse.SUPPRESS_HELP)
+
     build_group.add_option('--with-pkg-config', action='store_true', default=None,
                            help=optparse.SUPPRESS_HELP)
     build_group.add_option('--without-pkg-config', dest='with_pkg_config', action='store_false',
@@ -1869,6 +1872,16 @@ def create_template_vars(source_paths, build_paths, options, modules, cc, arch,
             return path
         return os.path.join(build_dir, path)
 
+    def all_targets():
+        yield 'libs'
+        yield 'cli'
+        yield 'tests'
+        yield 'docs'
+        if options.build_fuzzers:
+            yield 'fuzzers'
+        if options.build_bogo_shim:
+            yield 'bogo_shim'
+
     variables = {
         'version_major':  Version.major(),
         'version_minor':  Version.minor(),
@@ -1885,6 +1898,8 @@ def create_template_vars(source_paths, build_paths, options, modules, cc, arch,
         'macos_so_compat_ver': '%s.%s.0' % (Version.packed(), Version.so_rev()),
         'macos_so_current_ver': '%s.%s.%s' % (Version.packed(), Version.so_rev(), Version.patch()),
 
+        'all_targets': ' '.join(all_targets()),
+
         'base_dir': source_paths.base_dir,
         'src_dir': source_paths.src_dir,
         'doc_dir': source_paths.doc_dir,
@@ -2013,6 +2028,9 @@ def create_template_vars(source_paths, build_paths, options, modules, cc, arch,
         'include_paths': build_paths.format_include_paths(cc, options.with_external_includedir),
         'module_defines': sorted(flatten([m.defines() for m in modules])),
 
+        'build_bogo_shim': options.build_bogo_shim,
+        'bogo_shim_src': os.path.join(source_paths.src_dir, 'bogo_shim', 'bogo_shim.cpp'),
+
         'os_features': osinfo.enabled_features(options),
         'os_name': osinfo.basename,
         'cpu_features': arch.supported_isa_extensions(cc, options),
diff --git a/src/bogo_shim/bogo_shim.cpp b/src/bogo_shim/bogo_shim.cpp
new file mode 100644
index 000000000..9266fe5a5
--- /dev/null
+++ b/src/bogo_shim/bogo_shim.cpp
@@ -0,0 +1,1487 @@
+/*
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+/*
+* This is a shim for testing Botan against BoringSSL's test TLS stack (BoGo).
+*
+* Instructions on use should go here.
+*/
+
+#include <botan/tls_client.h>
+#include <botan/tls_server.h>
+#include <botan/tls_exceptn.h>
+#include <botan/tls_algos.h>
+#include <botan/data_src.h>
+#include <botan/pkcs8.h>
+#include <botan/oids.h>
+#include <botan/chacha_rng.h>
+#include <botan/base64.h>
+#include <botan/hex.h>
+#include <botan/parsing.h>
+#include <botan/mem_ops.h>
+#include <iostream>
+#include <vector>
+#include <string>
+#include <map>
+#include <set>
+#include <ctime>
+#include <unordered_map>
+
+#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
+  #include <sys/socket.h>
+  #include <sys/time.h>
+  #include <netinet/in.h>
+  #include <netdb.h>
+  #include <string.h>
+  #include <unistd.h>
+  #include <errno.h>
+  #include <fcntl.h>
+#endif
+
+namespace {
+
+int shim_output(const std::string& s, int rc = 0)
+   {
+   std::cout << s << "\n";
+   return rc;
+   }
+
+void shim_exit_with_error(const std::string& s, int rc = 1)
+   {
+   std::cerr << s << "\n";
+   std::exit(rc);
+   }
+
+void shim_log(const std::string& s)
+   {
+   static FILE* f = fopen("/tmp/bogo_shim.log", "w");
+   fprintf(f, "%d: %s\n", (int)time(nullptr), s.c_str());
+   fflush(f);
+   }
+
+std::string map_to_bogo_error(const std::string& e)
+   {
+   static const std::unordered_map<std::string, std::string> err_map
+      {
+         { "Application data before handshake done", ":APPLICATION_DATA_INSTEAD_OF_HANDSHAKE:" },
+         { "Bad Hello_Request, has non-zero size", ":BAD_HELLO_REQUEST:" },
+         { "Bad code for TLS alert level", ":UNKNOWN_ALERT_TYPE:" },
+         { "Bad extension size", ":DECODE_ERROR:" },
+         { "Bad signature on server key exchange", ":BAD_SIGNATURE:" },
+         { "Bad size (1) for TLS alert message", ":BAD_ALERT:" },
+         { "Bad size (4) for TLS alert message", ":BAD_ALERT:" },
+         { "CERTIFICATE decoding failed with PEM: No PEM header found", ":CANNOT_PARSE_LEAF_CERT:" },
+         { "Can't agree on a ciphersuite with client", ":NO_SHARED_CIPHER:" },
+         { "Can't interleave application and handshake data", ":UNEXPECTED_RECORD:" },
+         { "Certificate chain exceeds policy specified maximum size", ":EXCESSIVE_MESSAGE_SIZE:" },
+         { "Certificate key type did not match ciphersuite", ":WRONG_CERTIFICATE_TYPE:" },
+         { "Certificate: Message malformed", ":DECODE_ERROR:" },
+         { "Client cert verify failed", ":BAD_SIGNATURE:" },
+         { "Client did not offer NULL compression", ":INVALID_COMPRESSION_LIST:" },
+         { "Client offered version with major version under 3", ":UNSUPPORTED_PROTOCOL:" },
+         { "Client policy prohibits insecure renegotiation", ":RENEGOTIATION_MISMATCH:" },
+         { "Client policy prohibits renegotiation", ":NO_RENEGOTIATION:" },
+         { "Client resumed extended ms session without sending extension", ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:" },
+         { "Client signalled fallback SCSV, possible attack", ":INAPPROPRIATE_FALLBACK:" },
+         { "Client version TLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" },
+         { "Client version TLS v1.1 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" },
+         { "Client: No certificates sent by server", ":DECODE_ERROR:" },
+         { "Counterparty sent inconsistent key and sig types", ":WRONG_SIGNATURE_TYPE:" },
+         { "Empty ALPN protocol not allowed", ":PARSE_TLSEXT:" },
+         { "Encoding error: Cannot encode PSS string, output length too small", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
+         { "Finished message didn't verify", ":DIGEST_CHECK_FAILED:" },
+         { "Inconsistent length in certificate request", ":DECODE_ERROR:" },
+         { "Invalid CertificateRequest: Length field outside parameters", ":DECODE_ERROR:" },
+         { "Invalid CertificateVerify: Extra bytes at end of message", ":DECODE_ERROR:" },
+         { "Invalid Certificate_Status: invalid length field", ":DECODE_ERROR:" },
+         { "Invalid ChangeCipherSpec", ":BAD_CHANGE_CIPHER_SPEC:" },
+         { "Invalid ClientKeyExchange: Extra bytes at end of message", ":DECODE_ERROR:" },
+         { "Invalid ServerKeyExchange: Extra bytes at end of message", ":DECODE_ERROR:" },
+         { "Invalid SessionTicket: Extra bytes at end of message", ":DECODE_ERROR:" },
+         { "Invalid authentication tag: ChaCha20Poly1305 tag check failed", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" },
+         { "Invalid authentication tag: GCM tag check failed", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" },
+         { "Message authentication failure", ":DECRYPTION_FAILED_OR_BAD_RECORD_MAC:" },
+         { "OS2ECP: Unknown format type 251", ":BAD_ECPOINT:" },
+         { "Policy forbids all available TLS version", ":NO_SUPPORTED_VERSIONS_ENABLED:" },
+         { "Policy refuses to accept signing with any hash supported by peer", ":NO_COMMON_SIGNATURE_ALGORITHMS:" },
+         { "Policy requires client send a certificate, but it did not", ":PEER_DID_NOT_RETURN_A_CERTIFICATE:" },
+         { "Received a record that exceeds maximum size", ":ENCRYPTED_LENGTH_TOO_LONG:" },
+         { "Received unexpected record version in initial record", ":WRONG_VERSION_NUMBER:" },
+         { "Received unexpected record version", ":WRONG_VERSION_NUMBER:" },
+         { "Server certificate changed during renegotiation", ":SERVER_CERT_CHANGED:" },
+         { "Server changed its mind about extended master secret", ":RENEGOTIATION_EMS_MISMATCH:" },
+         { "Server changed its mind about secure renegotiation", ":RENEGOTIATION_MISMATCH:" },
+         { "Server changed version after renegotiation", ":WRONG_SSL_VERSION:" },
+         { "Server downgraded version after renegotiation", ":WRONG_SSL_VERSION:" },
+         { "Server replied using a ciphersuite not allowed in version it offered", ":WRONG_CIPHER_RETURNED:" },
+         { "Server replied with ciphersuite we didn't send", ":WRONG_CIPHER_RETURNED:" },
+         { "Server replied with later version than client offered", ":UNSUPPORTED_PROTOCOL:" },
+         { "Server replied with non-null compression method", ":UNSUPPORTED_COMPRESSION_ALGORITHM:" },
+         { "Server replied with some unknown ciphersuite", ":UNKNOWN_CIPHER_RETURNED:" },
+         { "Server replied with unsupported extensions: 0", ":UNEXPECTED_EXTENSION:" },
+         { "Server replied with unsupported extensions: 1234", ":UNEXPECTED_EXTENSION:" },
+         { "Server replied with unsupported extensions: 16", ":UNEXPECTED_EXTENSION:" },
+         { "Server replied with unsupported extensions: 43", ":UNEXPECTED_EXTENSION:" },
+         { "Server replied with unsupported extensions: 5", ":UNEXPECTED_EXTENSION:" },
+         { "Server resumed session and removed extended master secret", ":RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION:" },
+         { "Server resumed session but added extended master secret", ":RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION:" },
+         { "Server resumed session but with wrong version", ":OLD_SESSION_VERSION_NOT_RETURNED:" },
+         { "Server sent ECC curve prohibited by policy", ":WRONG_CURVE:" },
+         { "Server sent an unsupported extension", ":UNEXPECTED_EXTENSION:" },
+         { "Server sent bad values for secure renegotiation", ":RENEGOTIATION_MISMATCH:" },
+         { "Server version TLS v1.0 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" },
+         { "Server version TLS v1.1 is unacceptable by policy", ":UNSUPPORTED_PROTOCOL:" },
+         { "Server_Hello_Done: Must be empty, and is not", ":DECODE_ERROR:" },
+         { "Simulated OCSP callback failure", ":OCSP_CB_ERROR:" },
+         { "Simulating cert verify callback failure", ":CERT_CB_ERROR:" },
+         { "TLS plaintext record is larger than allowed maximum", ":DATA_LENGTH_TOO_LONG:" },
+         { "TLS signature extension did not allow for RSA/SHA-256 signature", ":WRONG_SIGNATURE_TYPE:", },
+         { "Test requires rejecting cert", ":CERTIFICATE_VERIFY_FAILED:" },
+         { "Unexpected ALPN protocol", ":INVALID_ALPN_PROTOCOL:" },
+         { "Unexpected record type 42 from counterparty", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected  received hello_request", ":UNEXPECTED_MESSAGE:" },
+         { "Unexpected state transition in handshake, expected  received server_key_exchange", ":BAD_HELLO_REQUEST:" },
+         { "Unexpected state transition in handshake, expected certificate received certificate_status", ":UNEXPECTED_MESSAGE:" },
+         { "Unexpected state transition in handshake, expected certificate received client_hello+client_key_exchange", ":UNEXPECTED_MESSAGE:" },
+         { "Unexpected state transition in handshake, expected certificate_request|server_hello_done received certificate+certificate_status+server_key_exchange", ":UNEXPECTED_MESSAGE:" },
+         { "Unexpected state transition in handshake, expected certificate_verify received client_hello+certificate+client_key_exchange+change_cipher_spec", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected change_cipher_spec received certificate+certificate_status+server_hello_done+new_session_ticket+finished", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected change_cipher_spec received certificate+certificate_status+server_key_exchange+server_hello_done+new_session_ticket+finished", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected change_cipher_spec received client_hello+client_key_exchange+finished", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected change_cipher_spec received client_hello+finished", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected change_cipher_spec received finished", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected client_key_exchange received client_hello+change_cipher_spec", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected new_session_ticket received certificate+certificate_status+server_hello_done+change_cipher_spec", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected new_session_ticket received certificate+certificate_status+server_key_exchange+server_hello_done+change_cipher_spec", ":UNEXPECTED_RECORD:" },
+         { "Unexpected state transition in handshake, expected server_hello_done received server_key_exchange+certificate_request", ":UNEXPECTED_MESSAGE:" },
+         { "Unexpected state transition in handshake, expected server_key_exchange received certificate", ":WRONG_CIPHER_RETURNED:" },
+         { "Unexpected state transition in handshake, expected server_key_exchange received certificate+certificate_status+server_hello_done", ":UNEXPECTED_MESSAGE:" },
+         { "Unexpected state transition in handshake, expected server_key_exchange|server_hello_done received certificate_request", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 43", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 44", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 46", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 53", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 54", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 55", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 56", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 57", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 58", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 6", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 62", ":UNEXPECTED_MESSAGE:" },
+         { "Unknown TLS handshake message type 64", ":UNEXPECTED_MESSAGE:" },
+         { "signature_algorithm_of_scheme: Unknown signature algorithm enum", ":WRONG_SIGNATURE_TYPE:" },
+      };
+
+   auto err_map_i = err_map.find(e);
+   if(err_map_i != err_map.end())
+      return err_map_i->second;
+
+   return "Unmapped error: " + e;
+   }
+
+class Shim_Exception final : public std::exception
+   {
+   public:
+      Shim_Exception(const std::string& msg, int rc = 1) :
+         m_msg(msg), m_rc(rc) {}
+
+      const char* what() const noexcept { return m_msg.c_str(); }
+
+      int rc() const { return m_rc; }
+   private:
+      const std::string m_msg;
+      int m_rc;
+   };
+
+#if defined(BOTAN_TARGET_OS_HAS_SOCKETS)
+
+class Shim_Socket final
+   {
+   private:
+      typedef int socket_type;
+      typedef ssize_t socket_op_ret_type;
+      static void close_socket(socket_type s) { ::close(s); }
+      static std::string get_last_socket_error() { return ::strerror(errno); }
+
+   public:
+      Shim_Socket(const std::string& hostname, int port) : m_socket(-1)
+         {
+         addrinfo hints;
+         std::memset(&hints, 0, sizeof(hints));
+         hints.ai_family = AF_UNSPEC;
+         hints.ai_socktype = SOCK_STREAM;
+         hints.ai_flags = AI_NUMERICSERV;
+         addrinfo* res;
+
+         const std::string service = std::to_string(port);
+         int rc = ::getaddrinfo(hostname.c_str(), service.c_str(), &hints, &res);
+         shim_log("Connecting " + hostname + ":" + service);
+
+         if(rc != 0)
+            {
+            throw Shim_Exception("Name resolution failed for " + hostname);
+            }
+
+         for(addrinfo* rp = res; (m_socket == -1) && (rp != nullptr); rp = rp->ai_next)
+            {
+            if(rp->ai_family != AF_INET && rp->ai_family != AF_INET6)
+               continue;
+
+            m_socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+
+            if(m_socket == -1)
+               {
+               // unsupported socket type?
+               continue;
+               }
+
+            int err = ::connect(m_socket, rp->ai_addr, rp->ai_addrlen);
+
+            if(err != 0)
+               {
+               ::close(m_socket);
+               m_socket = -1;
+               }
+            }
+
+         if(m_socket < 0)
+            throw Shim_Exception("Failed to connect to host");
+         }
+
+      ~Shim_Socket()
+         {
+         ::close(m_socket);
+         m_socket = -1;
+         }
+
+      void write(const uint8_t buf[], size_t len)
+         {
+         if(m_socket < 0)
+            throw Shim_Exception("Socket was bad on write");
+         size_t sent_so_far = 0;
+         while(sent_so_far != len)
+            {
+            const size_t left = len - sent_so_far;
+            socket_op_ret_type sent = ::send(m_socket, Botan::cast_uint8_ptr_to_char(&buf[sent_so_far]), left, 0);
+            if(sent < 0)
+               throw Shim_Exception("Socket write failed", errno);
+            else
+               sent_so_far += static_cast<size_t>(sent);
+            }
+         }
+
+      size_t read(uint8_t buf[], size_t len)
+         {
+         if(m_socket < 0)
+            throw Shim_Exception("Socket was bad on read");
+         socket_op_ret_type got = ::recv(m_socket, Botan::cast_uint8_ptr_to_char(buf), len, 0);
+         //shim_log("Read returned " + std::to_string(got));
+
+         if(got < 0)
+            {
+            if(errno == ECONNRESET)
+               return 0;
+            throw Shim_Exception("Socket read failed: " + std::string(strerror(errno)));
+            }
+
+         return static_cast<size_t>(got);
+         }
+
+   private:
+      socket_type m_socket;
+   };
+
+#endif
+
+std::set<std::string> combine_options(
+   const std::set<std::string>& a,
+   const std::set<std::string>& b,
+   const std::set<std::string>& c,
+   const std::set<std::string>& d)
+   {
+   std::set<std::string> combined;
+
+   for(auto i : a)
+      combined.insert(i);
+   for(auto i : b)
+      combined.insert(i);
+   for(auto i : c)
+      combined.insert(i);
+   for(auto i : d)
+      combined.insert(i);
+
+   return combined;
+   }
+
+class Shim_Arguments final
+   {
+   public:
+      Shim_Arguments(const std::set<std::string>& flags,
+                     const std::set<std::string>& string_opts,
+                     const std::set<std::string>& base64_opts,
+                     const std::set<std::string>& int_opts,
+                     const std::set<std::string>& int_vec_opts) :
+         m_flags(flags),
+         m_string_opts(string_opts),
+         m_base64_opts(base64_opts),
+         m_int_opts(int_opts),
+         m_int_vec_opts(int_vec_opts),
+         m_all_options(combine_options(string_opts, base64_opts, int_opts, int_vec_opts))
+         {}
+
+      void parse_args(char* argv[]);
+
+      bool flag_set(const std::string& flag) const
+         {
+         if(m_flags.count(flag) == 0)
+            throw Shim_Exception("Unknown bool flag " + flag);
+
+         return m_parsed_flags.count(flag);
+         }
+
+      std::string get_string_opt(const std::string& key) const
+         {
+         if(m_string_opts.count(key) == 0)
+            throw Shim_Exception("Unknown string key " + key);
+         return get_opt(key);
+         }
+
+      std::string get_string_opt_or_else(const std::string& key, const std::string& def) const
+         {
+         if(m_string_opts.count(key) == 0)
+            throw Shim_Exception("Unknown string key " + key);
+         if(!option_used(key))
+            return def;
+         return get_opt(key);
+         }
+
+      std::vector<uint8_t> get_b64_opt(const std::string& key) const
+         {
+         if(m_base64_opts.count(key) == 0)
+            throw Shim_Exception("Unknown base64 key " + key);
+         return Botan::unlock(Botan::base64_decode(get_opt(key)));
+         }
+
+      size_t get_int_opt(const std::string& key) const
+         {
+         if(m_int_opts.count(key) == 0)
+            throw Shim_Exception("Unknown int key " + key);
+         return Botan::to_u32bit(get_opt(key));
+         }
+
+      size_t get_int_opt_or_else(const std::string& key, size_t def) const
+         {
+         if(m_int_opts.count(key) == 0)
+            throw Shim_Exception("Unknown int key " + key);
+         if(!option_used(key))
+            return def;
+
+         return Botan::to_u32bit(get_opt(key));
+         }
+
+      std::vector<size_t> get_int_vec_opt(const std::string& key) const
+         {
+         if(m_int_vec_opts.count(key) == 0)
+            throw Shim_Exception("Unknown int vec key " + key);
+
+         auto i = m_parsed_int_vec_opts.find(key);
+         if(i == m_parsed_int_vec_opts.end())
+            return std::vector<size_t>();
+         else
+            return i->second;
+         }
+
+      std::vector<std::string> get_alpn_string_vec_opt(const std::string& option) const
+         {
+         // hack used for alpn list (relies on all ALPNs being 3 chars long...)
+         char delim = 0x03;
+
+         if(option_used(option))
+            return Botan::split_on(get_string_opt(option), delim);
+         else
+            return std::vector<std::string>();
+         }
+
+      bool option_used(const std::string& key) const
+         {
+         if(m_parsed_opts.find(key) != m_parsed_opts.end())
+            return true;
+         if(m_parsed_int_vec_opts.find(key) != m_parsed_int_vec_opts.end())
+            return true;
+         return false;
+         }
+
+   private:
+      std::string get_opt(const std::string& key) const
+         {
+         auto i = m_parsed_opts.find(key);
+         if(i == m_parsed_opts.end())
+            throw Shim_Exception("Option " + key + " was not provided");
+         return i->second;
+         }
+
+      const std::set<std::string> m_flags;
+      const std::set<std::string> m_string_opts;
+      const std::set<std::string> m_base64_opts;
+      const std::set<std::string> m_int_opts;
+      const std::set<std::string> m_int_vec_opts;
+      const std::set<std::string> m_all_options;
+
+      std::set<std::string> m_parsed_flags;
+      std::map<std::string, std::string> m_parsed_opts;
+      std::map<std::string, std::vector<size_t>> m_parsed_int_vec_opts;
+   };
+
+void Shim_Arguments::parse_args(char* argv[])
+   {
+   int i = 1; // skip argv[0]
+
+   while(argv[i] != nullptr)
+      {
+      const std::string param(argv[i]);
+
+      if(param.find("-") == 0)
+         {
+         const std::string flag_name = param.substr(1, std::string::npos);
+
+         if(m_flags.count(flag_name))
+            {
+            shim_log("flag " + flag_name);
+            m_parsed_flags.insert(flag_name);
+            i += 1;
+            }
+         else if(m_all_options.count(flag_name))
+            {
+            if(argv[i+1] == nullptr)
+               throw Shim_Exception("Expected argument following " + param);
+            std::string val(argv[i+1]);
+            shim_log("param " + flag_name + "=" + val);
+
+            if(m_int_vec_opts.count(flag_name))
+               {
+               const size_t v = Botan::to_u32bit(val);
+               m_parsed_int_vec_opts[flag_name].push_back(v);
+               }
+            else
+               {
+               m_parsed_opts[flag_name] = val;
+               }
+            i += 2;
+            }
+         else
+            {
+            shim_log("Unknown option " + param);
+            throw Shim_Exception("Unknown option " + param, 89);
+            }
+         }
+      else
+         {
+         shim_log("Unknown option " + param);
+         throw Shim_Exception("Unknown option " + param, 89);
+         }
+      }
+   }
+
+std::unique_ptr<Shim_Arguments> parse_options(char* argv[])
+   {
+   const std::set<std::string> bogo_shim_flags = {
+      "allow-false-start-without-alpn",
+      "allow-unknown-alpn-protos",
+      "async",
+      "cbc-record-splitting",
+      "check-close-notify",
+      "decline-alpn",
+      "decline-ocsp-callback",
+      "dtls",
+      "enable-all-curves",
+      "enable-channel-id",
+      "enable-early-data",
+      "enable-ed25519",
+      "enable-grease",
+      "enable-ocsp-stapling",
+      "enable-signed-cert-timestamps",
+      //"enforce-rsa-key-usage",
+      //"expect-accept-early-data",
+      "expect-extended-master-secret",
+      "expect-no-offer-early-data",
+      "expect-no-secure-renegotiation",
+      "expect-no-session",
+      "expect-no-session-id",
+      //"expect-reject-early-data",
+      "expect-secure-renegotiation",
+      "expect-session-id",
+      "expect-session-miss",
+      "expect-sha256-client-cert",
+      "expect-ticket-renewal",
+      "expect-ticket-supports-early-data",
+      //"expect-tls13-downgrade",
+      "expect-verify-result",
+      //"export-traffic-secrets",
+      "fail-cert-callback",
+      //"fail-ddos-callback",
+      //"fail-early-callback",
+      "fail-ocsp-callback",
+      "fallback-scsv",
+      //"false-start",
+      "forbid-renegotiation-after-handshake",
+      "handoff",
+      "handshake-never-done",
+      "handshake-twice",
+      "handshaker-resume",
+      //"ignore-tls13-downgrade",
+      "implicit-handshake",
+      "install-cert-compression-algs",
+      "install-ddos-callback",
+      "is-handshaker-supported",
+      //"jdk11-workaround",
+      //"key-update",
+      "no-op-extra-handshake",
+      "no-rsa-pss-rsae-certs",
+      "no-ticket",
+      "no-tls1",
+      "no-tls11",
+      "no-tls12",
+      "no-tls13", // implict due to 1.3 not being implemented
+      //"on-resume-verify-fail",
+      //"partial-write",
+      //"peek-then-read",
+      //"read-with-unfinished-write",
+      "renegotiate-freely",
+      "renegotiate-ignore",
+      "renegotiate-once",
+      //"renew-ticket",
+      "require-any-client-certificate",
+      "retain-only-sha256-client-cert",
+      //"reverify-on-resume",
+      "select-empty-alpn",
+      "send-alert",
+      "server",
+      "server-preference",
+      //"set-ocsp-in-callback",
+      //"shim-shuts-down",
+      "shim-writes-first",
+      //"tls-unique",
+      "use-custom-verify-callback",
+      //"use-early-callback",
+      "use-export-context",
+      //"use-exporter-between-reads",
+      "use-ocsp-callback",
+      //"use-old-client-cert-callback",
+      //"use-ticket-callback",
+      "verify-fail",
+      "verify-peer",
+      //"verify-peer-if-no-obc",
+      "write-different-record-sizes",
+   };
+
+   const std::set<std::string> bogo_shim_string_opts = {
+      "advertise-alpn",
+      //"advertise-npn",
+      "cert-file",
+      "cipher",
+      //"delegated-credential",
+      "expect-advertised-alpn",
+      "expect-alpn",
+      "expect-client-ca-list",
+      "expect-late-alpn",
+      "expect-msg-callback",
+      //"expect-next-proto",
+      "expect-peer-cert-file",
+      "expect-server-name",
+      "export-context",
+      "export-label",
+      "handshaker-path",
+      "host-name",
+      "key-file",
+      "psk",
+      "psk-identity",
+      "select-alpn",
+      "select-next-proto",
+      //"send-channel-id",
+      "srtp-profiles",
+      "use-client-ca-list",
+      //"write-settings",
+   };
+
+   const std::set<std::string> bogo_shim_base64_opts = {
+      "expect-certificate-types",
+      //"expect-channel-id",
+      "expect-ocsp-response",
+      //"expect-quic-transport-params",
+      //"expect-signed-cert-timestamps",
+      //"ocsp-response",
+      //"quic-transport-params",
+      //"signed-cert-timestamps",
+      //"ticket-key", /* we use a different ticket format from Boring */
+      //"token-binding-params",
+   };
+
+   const std::set<std::string> bogo_shim_int_opts {
+      "expect-cipher-aes",
+      "expect-cipher-no-aes",
+      "expect-curve-id",
+      "expect-peer-signature-algorithm",
+      "expect-ticket-age-skew",
+      "expect-token-binding-param",
+      "expect-total-renegotiations",
+      "expect-version",
+      //"export-early-keying-material",
+      "export-keying-material",
+      "initial-timeout-duration-ms",
+      "max-cert-list",
+      //"max-send-fragment",
+      "max-version",
+      "min-version",
+      //"mtu",
+      "port",
+      "read-size",
+      "resume-count",
+      "resumption-delay",
+   };
+
+   const std::set<std::string> bogo_shim_int_vec_opts {
+      "curves",
+      "expect-peer-verify-pref",
+      "signing-prefs",
+      "verify-prefs",
+   };
+
+   std::unique_ptr<Shim_Arguments> args(
+      new Shim_Arguments(bogo_shim_flags,
+                         bogo_shim_string_opts,
+                         bogo_shim_base64_opts,
+                         bogo_shim_int_opts,
+                         bogo_shim_int_vec_opts));
+
+   // may throw:
+   args->parse_args(argv);
+
+   return args;
+   }
+
+class Shim_Policy final : public Botan::TLS::Policy
+   {
+   public:
+      Shim_Policy(const Shim_Arguments& args) : m_args(args), m_sessions(0) {}
+
+      void incr_session_established() { m_sessions += 1; }
+      size_t sessions_established() const { return m_sessions; }
+
+      std::vector<std::string> allowed_ciphers() const override
+         {
+         return {
+            "AES-256/OCB(12)",
+            "AES-128/OCB(12)",
+            "ChaCha20Poly1305",
+            "AES-256/GCM",
+            "AES-128/GCM",
+            "AES-256/CCM",
+            "AES-128/CCM",
+            "AES-256/CCM(8)",
+            "AES-128/CCM(8)",
+            "Camellia-256/GCM",
+            "Camellia-128/GCM",
+            "ARIA-256/GCM",
+            "ARIA-128/GCM",
+            "AES-256",
+            "AES-128",
+            "Camellia-256",
+            "Camellia-128",
+            "SEED",
+            "3DES",
+         };
+
+         }
+
+      std::vector<std::string> allowed_signature_hashes() const override
+         {
+         if(m_args.option_used("signing-prefs"))
+            {
+            std::vector<std::string> pref_hash;
+            for(size_t pref : m_args.get_int_vec_opt("signing-prefs"))
+               {
+               const auto scheme = static_cast<Botan::TLS::Signature_Scheme>(pref);
+               if(Botan::TLS::signature_scheme_is_known(scheme) == false)
+                  continue;
+               pref_hash.push_back(Botan::TLS::hash_function_of_scheme(scheme));
+               }
+
+            if(m_args.flag_set("server"))
+               pref_hash.push_back("SHA-256");
+            return pref_hash;
+            }
+         else
+            {
+            return { "SHA-512", "SHA-384", "SHA-256", "SHA-1" };
+            }
+         }
+
+      //std::vector<std::string> allowed_macs() const override;
+
+      std::vector<std::string> allowed_key_exchange_methods() const override
+         {
+         return {
+            "ECDHE_PSK",
+            "DHE_PSK",
+            "PSK",
+            "CECPQ1",
+            "ECDH",
+            "DH",
+            "RSA",
+         };
+         }
+
+      std::vector<std::string> allowed_signature_methods() const override
+         {
+         return {
+            "ECDSA",
+            "RSA",
+            "IMPLICIT",
+         };
+
+         }
+
+      std::vector<Botan::TLS::Signature_Scheme> allowed_signature_schemes() const override
+         {
+         if(m_args.option_used("signing-prefs"))
+            {
+            std::vector<Botan::TLS::Signature_Scheme> schemes;
+            for(size_t pref : m_args.get_int_vec_opt("signing-prefs"))
+               {
+               schemes.push_back(static_cast<Botan::TLS::Signature_Scheme>(pref));
+               }
+
+            // BoGo gets sad if these are not included in our signature_algorithms extension
+
+            if(!m_args.flag_set("server") && false)
+               {
+               schemes.push_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256);
+               schemes.push_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256);
+               }
+
+            return schemes;
+            }
+
+         if(m_args.option_used("verify-prefs"))
+            {
+            std::vector<Botan::TLS::Signature_Scheme> schemes;
+            for(size_t pref : m_args.get_int_vec_opt("verify-prefs"))
+               {
+               schemes.push_back(static_cast<Botan::TLS::Signature_Scheme>(pref));
+               }
+
+            // BoGo gets sad if these are not included in our signature_algorithms extension
+
+            if(!m_args.flag_set("server") && false)
+               {
+               schemes.push_back(Botan::TLS::Signature_Scheme::RSA_PKCS1_SHA256);
+               schemes.push_back(Botan::TLS::Signature_Scheme::ECDSA_SHA256);
+               }
+
+            return schemes;
+            }
+
+         return Botan::TLS::Policy::allowed_signature_schemes();
+         }
+
+      //size_t minimum_signature_strength() const override;
+
+      //bool require_cert_revocation_info() const override;
+
+      std::vector<Botan::TLS::Group_Params> key_exchange_groups() const override
+         {
+         if(m_args.option_used("curves"))
+            {
+            std::vector<Botan::TLS::Group_Params> groups;
+            for(size_t pref : m_args.get_int_vec_opt("curves"))
+               {
+               groups.push_back(static_cast<Botan::TLS::Group_Params>(pref));
+               }
+
+            return groups;
+            }
+
+         return Botan::TLS::Policy::key_exchange_groups();
+         }
+
+      bool use_ecc_point_compression() const override { return false; } // BoGo expects this
+
+      //Botan::TLS::Group_Params choose_key_exchange_group(const std::vector<Botan::TLS::Group_Params>& peer_groups) const override;
+
+      bool require_client_certificate_authentication() const
+         {
+         return m_args.flag_set("require-any-client-certificate");
+         }
+
+      bool request_client_certificate_authentication() const
+         {
+         return m_args.flag_set("verify-peer") ||
+            m_args.flag_set("fail-cert-callback") ||
+            require_client_certificate_authentication();
+         }
+
+      bool allow_insecure_renegotiation() const override
+         {
+         if(m_args.flag_set("expect-no-secure-renegotiation"))
+            return true;
+         else
+            return false;
+         }
+
+      //bool include_time_in_hello_random() const override;
+
+      bool allow_client_initiated_renegotiation() const override
+         {
+         if(m_args.flag_set("renegotiate-freely"))
+            return true;
+
+         if(m_args.flag_set("renegotiate-once") && m_sessions <= 1)
+            return true;
+
+         return false;
+         }
+
+      bool allow_server_initiated_renegotiation() const override
+         {
+         return allow_client_initiated_renegotiation(); // same logic
+         }
+
+      bool allow_tls10() const override
+         {
+         return (!m_args.flag_set("no-tls1"));
+         }
+
+      bool allow_tls11() const override
+         {
+         return (!m_args.flag_set("no-tls11"));
+         }
+
+      bool allow_tls12() const override
+         {
+         return (!m_args.flag_set("no-tls12"));
+         }
+
+      //bool allow_dtls10() const override;
+
+      //bool allow_dtls12() const override;
+
+      //Botan::TLS::Group_Params default_dh_group() const override;
+
+      //size_t minimum_dh_group_size() const override;
+
+      size_t minimum_ecdsa_group_size() const override { return 224; }
+
+      size_t minimum_ecdh_group_size() const override { return 224; }
+
+      //size_t minimum_rsa_bits() const override;
+
+      //size_t minimum_dsa_group_size() const override;
+
+      //void check_peer_key_acceptable(const Botan::Public_Key& public_key) const override;
+
+      //bool hide_unknown_users() const override;
+
+      //uint32_t session_ticket_lifetime() const override;
+
+      //std::vector<uint16_t> srtp_profiles() const override;
+
+      bool only_resume_with_exact_version() const override
+         {
+         return false;
+         }
+
+      bool acceptable_protocol_version(Botan::TLS::Protocol_Version version) const override
+         {
+         if(!Botan::TLS::Policy::acceptable_protocol_version(version))
+            return false;
+
+         if(m_args.option_used("min-version"))
+            {
+            const uint16_t min_version_16 = static_cast<uint16_t>(m_args.get_int_opt("min-version"));
+            Botan::TLS::Protocol_Version min_version(min_version_16 >> 8, min_version_16 & 0xFF);
+            if(min_version > version)
+               return false;
+            }
+
+         if(m_args.option_used("max-version"))
+            {
+            const uint16_t max_version_16 = static_cast<uint16_t>(m_args.get_int_opt("max-version"));
+            Botan::TLS::Protocol_Version max_version(max_version_16 >> 8, max_version_16 & 0xFF);
+            if(version > max_version)
+               return false;
+            }
+
+         return version.known_version();
+         }
+
+      bool send_fallback_scsv(Botan::TLS::Protocol_Version) const override
+         {
+         return m_args.flag_set("fallback-scsv");
+         }
+
+      //bool server_uses_own_ciphersuite_preferences() const override;
+
+      //bool negotiate_encrypt_then_mac() const override;
+
+      bool support_cert_status_message() const override
+         {
+         if(m_args.flag_set("server"))
+            return false;
+         return true;
+         }
+
+      std::vector<uint16_t> ciphersuite_list(Botan::TLS::Protocol_Version version,
+                                             bool have_srp) const override;
+
+      //size_t dtls_default_mtu() const override;
+
+      //size_t dtls_initial_timeout() const override;
+
+      //size_t dtls_maximum_timeout() const override;
+
+      bool allow_resumption_for_renegotiation() const
+         {
+         return false; // BoGo expects this
+         }
+
+      bool abort_connection_on_undesired_renegotiation() const
+         {
+         if(m_args.flag_set("renegotiate-ignore"))
+            return false;
+         else
+            return true;
+         }
+
+      size_t maximum_certificate_chain_size() const
+         {
+         return m_args.get_int_opt_or_else("max-cert-list", 0);
+         }
+
+   private:
+      const Shim_Arguments& m_args;
+      size_t m_sessions;
+   };
+
+std::vector<uint16_t> Shim_Policy::ciphersuite_list(Botan::TLS::Protocol_Version version,
+                                                    bool have_srp) const
+   {
+   std::vector<uint16_t> ciphersuite_codes;
+
+   const std::string cipher_limit = m_args.get_string_opt_or_else("cipher", "");
+   if(cipher_limit == "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384|TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256|TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA]:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CBC_SHA:[TLS_RSA_WITH_AES_256_GCM_SHA384|TLS_RSA_WITH_AES_256_CBC_SHA]")
+      {
+      std::vector<std::string> suites = {
+         "ECDHE_RSA_WITH_AES_128_GCM_SHA256",
+         "ECDHE_RSA_WITH_AES_256_GCM_SHA384",
+         "ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
+         "ECDHE_RSA_WITH_AES_256_CBC_SHA",
+         "RSA_WITH_AES_256_GCM_SHA384",
+         "RSA_WITH_AES_256_CBC_SHA",
+      };
+
+      for(auto suite_name : suites)
+         {
+         const auto suite = Botan::TLS::Ciphersuite::from_name(suite_name);
+         if(suite.valid() == false)
+            shim_exit_with_error("Bad ciphersuite name " + suite_name);
+         ciphersuite_codes.push_back(suite.ciphersuite_code());
+         }
+      }
+   else
+      {
+      // Hack: go in reverse order to avoid preferring 3DES
+      auto ciphersuites = Botan::TLS::Ciphersuite::all_known_ciphersuites();
+      for(auto i = ciphersuites.rbegin(); i != ciphersuites.rend(); ++i)
+         {
+         const auto suite = *i;
+         // Can we use it?
+         if(suite.valid() == false)
+            continue;
+
+         // Are we doing SRP?
+         if(!have_srp && suite.kex_method() == Botan::TLS::Kex_Algo::SRP_SHA)
+            continue;
+
+         if(cipher_limit != "")
+            {
+            if(cipher_limit == "DEFAULT:!AES")
+               {
+               const std::string suite_algo = suite.cipher_algo();
+
+               if(suite_algo == "AES-128" || suite_algo == "AES-256" ||
+                  suite_algo == "AES-128/GCM" || suite_algo == "AES-256/GCM" ||
+                  suite_algo == "AES-128/CCM" || suite_algo == "AES-256/CCM" ||
+                  suite_algo == "AES-128/CCM(8)" || suite_algo == "AES-256/CCM(8)" ||
+                  suite_algo == "AES-128/OCB(12)" || suite_algo == "AES-256/OCB(12)")
+                  {
+                  continue;
+                  }
+               }
+            else
+               {
+               shim_exit_with_error("Unknown cipher " + cipher_limit);
+               }
+            }
+
+         if(!version.supports_aead_modes())
+            {
+            // Are we doing AEAD in a non-AEAD version?
+            if(suite.mac_algo() == "AEAD")
+               continue;
+
+            // Older (v1.0/v1.1) versions also do not support any hash but SHA-1
+            if(suite.mac_algo() != "SHA-1")
+               continue;
+            }
+
+         ciphersuite_codes.push_back(suite.ciphersuite_code());
+         }
+      }
+
+   return ciphersuite_codes;
+   }
+
+class Shim_Credentials final : public Botan::Credentials_Manager
+   {
+   public:
+      Shim_Credentials(const Shim_Arguments& args) : m_args(args)
+         {
+         m_psk_identity = m_args.get_string_opt_or_else("psk-identity", "");
+
+         const std::string psk_str = m_args.get_string_opt_or_else("psk", "");
+         m_psk = Botan::SymmetricKey(reinterpret_cast<const uint8_t*>(psk_str.data()), psk_str.size());
+
+         if(m_args.option_used("key-file") && m_args.option_used("cert-file"))
+            {
+            Botan::DataSource_Stream key_stream(m_args.get_string_opt("key-file"));
+            m_key = Botan::PKCS8::load_key(key_stream);
+
+            Botan::DataSource_Stream cert_stream(m_args.get_string_opt("cert-file"));
+
+            while(!cert_stream.end_of_data())
+               {
+               try
+                  {
+                  m_cert_chain.push_back(Botan::X509_Certificate(cert_stream));
+                  }
+               catch(...) {}
+               }
+            }
+         }
+
+      std::string psk_identity(const std::string& /*type*/,
+                               const std::string& /*context*/,
+                               const std::string& /*identity_hint*/) override
+         {
+         return m_psk_identity;
+         }
+
+      std::string psk_identity_hint(const std::string& /*type*/,
+                                    const std::string& /*context*/)
+         {
+         return m_psk_identity;
+         }
+
+      Botan::SymmetricKey psk(const std::string& type,
+                              const std::string& context,
+                              const std::string& identity) override
+         {
+         if(!m_args.flag_set("no-ticket") && type == "tls-server" && context == "session-ticket")
+            return Botan::SymmetricKey("ABCDEF0123456789");
+
+         if(identity != m_psk_identity)
+            throw Shim_Exception("Unexpected PSK identity");
+         return m_psk;
+         }
+
+      std::vector<Botan::X509_Certificate> cert_chain(
+         const std::vector<std::string>& cert_key_types,
+         const std::string& /*type*/,
+         const std::string& /*context*/)
+         {
+         if(m_args.flag_set("fail-cert-callback"))
+            throw std::runtime_error("Simulating cert verify callback failure");
+
+         if(m_key != nullptr && m_cert_chain.size() > 0)
+            {
+            for(std::string t : cert_key_types)
+               {
+               if(t == m_key->algo_name())
+                  return m_cert_chain;
+               }
+            }
+
+         return {};
+         }
+
+      Botan::Private_Key* private_key_for(const Botan::X509_Certificate& /*cert*/,
+                                          const std::string& /*type*/,
+                                          const std::string& /*context*/) override
+         {
+         // assumes cert == m_cert
+         return m_key.get();
+         }
+
+   private:
+      const Shim_Arguments& m_args;
+      Botan::SymmetricKey m_psk;
+      std::string m_psk_identity;
+      std::unique_ptr<Botan::Private_Key> m_key;
+      std::vector<Botan::X509_Certificate> m_cert_chain;
+   };
+
+class Shim_Callbacks final : public Botan::TLS::Callbacks
+   {
+   public:
+      Shim_Callbacks(const Shim_Arguments& args, Shim_Socket& socket, Shim_Policy& policy) :
+         m_channel(nullptr),
+         m_args(args),
+         m_policy(policy),
+         m_socket(socket),
+         m_is_datagram(args.flag_set("dtls")),
+         m_warning_alerts(0),
+         m_empty_records(0),
+         m_should_exit(false),
+         m_first_write_pending(true),
+         m_sent_close(false)
+         {}
+
+      void set_channel(Botan::TLS::Channel* channel)
+         {
+         m_channel = channel;
+         }
+
+      void tls_emit_data(const uint8_t data[], size_t size) override
+         {
+         //shim_log("Emit data len" + std::to_string(size));
+         m_socket.write(data, size);
+         }
+
+      void tls_record_received(uint64_t /*seq_no*/, const uint8_t data[], size_t size) override
+         {
+
+         if(size == 0)
+            {
+            m_empty_records += 1;
+            if(m_empty_records > 32)
+               shim_exit_with_error(":TOO_MANY_EMPTY_FRAGMENTS:");
+            }
+         else
+            {
+            m_empty_records = 0;
+            }
+         shim_log("Got a record len " + std::to_string(size));
+         if(m_first_write_pending)
+            {
+
+            //m_channel->send("hello");
+            m_first_write_pending = false;
+            }
+
+         std::vector<uint8_t> buf(data, data + size);
+         for(size_t i = 0; i != size; ++i)
+            buf[i] ^= 0xFF;
+
+         shim_log("Sending " + std::to_string(size) + " bytes of blob");
+         m_channel->send(buf);
+         }
+
+      bool tls_verify_message(const Botan::Public_Key& key,
+                              const std::string& emsa,
+                              Botan::Signature_Format format,
+                              const std::vector<uint8_t>& msg,
+                              const std::vector<uint8_t>& sig)
+         {
+         if(m_args.option_used("expect-peer-signature-algorithm"))
+            {
+            const auto scheme = static_cast<Botan::TLS::Signature_Scheme>(m_args.get_int_opt("expect-peer-signature-algorithm"));
+            if(scheme != Botan::TLS::Signature_Scheme::NONE)
+               {
+               const std::string exp_emsa = Botan::TLS::padding_string_for_scheme(scheme);
+               if(emsa != exp_emsa)
+                  shim_exit_with_error("Unexpected signature scheme got " + emsa + " expected " + exp_emsa);
+               }
+            }
+         return Botan::TLS::Callbacks::tls_verify_message(key, emsa, format, msg, sig);
+         }
+
+      void tls_verify_cert_chain(const std::vector<Botan::X509_Certificate>& /*cert_chain*/,
+                                 const std::vector<std::shared_ptr<const Botan::OCSP::Response>>& /*ocsp_responses*/,
+                                 const std::vector<Botan::Certificate_Store*>& /*trusted_roots*/,
+                                 Botan::Usage_Type /*usage*/,
+                                 const std::string& /*hostname*/,
+                                 const Botan::TLS::Policy& /*policy*/) override
+         {
+         if(m_args.flag_set("enable-ocsp-stapling") &&
+            m_args.flag_set("use-ocsp-callback") &&
+            m_args.flag_set("fail-ocsp-callback"))
+            {
+            throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::BAD_CERTIFICATE_STATUS_RESPONSE,
+                                            "Simulated OCSP callback failure");
+            }
+
+         if(m_args.flag_set("verify-peer") && m_args.flag_set("verify-fail"))
+            {
+            throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::BAD_CERTIFICATE,
+                                            "Test requires rejecting cert");
+            }
+         }
+
+      std::string tls_server_choose_app_protocol(const std::vector<std::string>& client_protos)
+         {
+         if(client_protos.empty())
+            return ""; // shouldn't happen?
+
+         if(m_args.flag_set("decline-alpn"))
+            return "";
+
+         if(m_args.option_used("expect-advertised-alpn"))
+            {
+            const std::vector<std::string> expected = m_args.get_alpn_string_vec_opt("expect-advertised-alpn");
+
+            if(client_protos != expected)
+               shim_exit_with_error("Bad ALPN from client");
+            }
+
+         if(m_args.option_used("select-alpn"))
+            return m_args.get_string_opt("select-alpn");
+
+         return client_protos[0]; // if not configured just pick something
+         }
+
+      void tls_alert(Botan::TLS::Alert alert) override
+         {
+         shim_log("Got an alert " + alert.type_string());
+
+         if(alert.type() == Botan::TLS::Alert::RECORD_OVERFLOW)
+            {
+            shim_exit_with_error(":TLSV1_ALERT_RECORD_OVERFLOW:");
+            }
+
+         if(!alert.is_fatal())
+            {
+            m_warning_alerts++;
+            if(m_warning_alerts > 5)
+               shim_exit_with_error(":TOO_MANY_WARNING_ALERTS:");
+            }
+
+         if(alert.type() == Botan::TLS::Alert::CLOSE_NOTIFY && m_sent_close == false)
+            {
+            m_channel->send_alert(alert);
+            m_sent_close = true;
+            }
+         }
+
+      bool tls_session_established(const Botan::TLS::Session& session) override
+         {
+         shim_log("Session established: " + Botan::hex_encode(session.session_id()) +
+                  " version " + session.version().to_string() +
+                  " cipher " + session.ciphersuite().to_string() +
+                  " EMS " + std::to_string(session.supports_extended_master_secret()));
+         // probably need tests here?
+
+         m_policy.incr_session_established();
+
+         if(m_args.option_used("expect-no-session-id"))
+            {
+            if(session.session_id().size() > 0)
+               shim_exit_with_error("GOT UNEXPECTED SESSION ID");
+            }
+
+         if(m_args.option_used("expect-version"))
+            {
+            if(session.version().version_code() != m_args.get_int_opt("expect-version"))
+               shim_exit_with_error("UNEXPECTED VERSION");
+            }
+
+         if(m_args.flag_set("expect-secure-renegotiation"))
+            {
+            if(m_channel->secure_renegotiation_supported() == false)
+               shim_exit_with_error("EXPECTED SECURE RENEGOTIATION");
+            }
+         else if(m_args.flag_set("expect-no-secure-renegotiation"))
+            {
+            if(m_channel->secure_renegotiation_supported() == true)
+               shim_exit_with_error("EXPECTED NO SECURE RENEGOTIATION");
+            }
+
+         if(m_args.flag_set("expect-extended-master-secret"))
+            {
+            if(session.supports_extended_master_secret() == false)
+               shim_exit_with_error("NO ETM");
+            }
+
+         return true;
+         }
+
+      void tls_session_activated() override
+         {
+         if(m_args.flag_set("send-alert"))
+            {
+            m_channel->send_fatal_alert(Botan::TLS::Alert::DECOMPRESSION_FAILURE);
+            return;
+            }
+
+         if(size_t length = m_args.get_int_opt_or_else("export-keying-material", 0))
+            {
+            const std::string label = m_args.get_string_opt("export-label");
+            const std::string context = m_args.get_string_opt("export-context");
+            const auto exported = m_channel->key_material_export(label, context, length);
+            m_channel->send(exported.bits_of());
+            shim_log("Sending " + std::to_string(length) + " bytes of export");
+            }
+
+         const std::string alpn = m_channel->application_protocol();
+
+         if(m_args.option_used("expect-alpn"))
+            {
+            if(alpn != m_args.get_string_opt("expect-alpn"))
+               shim_exit_with_error("Got unexpected ALPN");
+            }
+
+         if(alpn == "baz" && !m_args.flag_set("allow-unknown-alpn-protos"))
+            {
+            throw Botan::TLS::TLS_Exception(Botan::TLS::Alert::ILLEGAL_PARAMETER,
+                                            "Unexpected ALPN protocol");
+            }
+
+         if(m_args.flag_set("write-different-record-sizes"))
+            {
+            static const size_t record_sizes[] = {
+               0, 1, 255, 256, 257, 16383, 16384, 16385, 32767, 32768, 32769
+            };
+
+            std::vector<uint8_t> buf(32769, 0x42);
+
+            for(size_t sz : record_sizes)
+               {
+               m_channel->send(buf.data(), sz);
+               }
+
+            m_channel->close();
+            }
+         }
+
+      bool should_exit() const
+         {
+         return m_should_exit;
+         }
+
+   private:
+      Botan::TLS::Channel* m_channel;
+      const Shim_Arguments& m_args;
+      Shim_Policy& m_policy;
+      Shim_Socket& m_socket;
+      const bool m_is_datagram;
+      size_t m_warning_alerts;
+      size_t m_empty_records;
+      bool m_should_exit;
+      bool m_first_write_pending;
+      bool m_sent_close;
+   };
+
+}
+
+int main(int /*argc*/, char* argv[])
+   {
+   const std::chrono::milliseconds timeout(1000); // ??
+
+   try
+      {
+      std::unique_ptr<Shim_Arguments> args = parse_options(argv);
+
+      if(args->flag_set("is-handshaker-supported"))
+         {
+         return shim_output("No\n");
+         }
+
+      const uint16_t port = static_cast<uint16_t>(args->get_int_opt("port"));
+      const size_t resume_count = args->get_int_opt_or_else("resume-count", 0);
+      const bool is_server = args->flag_set("server");
+      const bool is_datagram = args->flag_set("dtls");
+
+      /*
+      if(is_server)
+         throw Shim_Exception("No support for server yet", 89);
+      */
+      if(is_datagram)
+         throw Shim_Exception("No support for DTLS yet", 89);
+
+      Botan::ChaCha_RNG rng(Botan::secure_vector<uint8_t>(64));
+      Botan::TLS::Session_Manager_In_Memory session_manager(rng, 1024);
+      Shim_Credentials creds(*args);
+
+      for(size_t i = 0; i != resume_count+1; ++i)
+         {
+         Shim_Socket socket("localhost", port);
+
+         Shim_Policy policy(*args);
+         Shim_Callbacks callbacks(*args, socket, policy);
+
+         std::unique_ptr<Botan::TLS::Channel> chan;
+
+         if(is_server)
+            {
+            chan.reset(new Botan::TLS::Server(callbacks, session_manager, creds, policy, rng, is_datagram));
+            }
+         else
+            {
+            Botan::TLS::Protocol_Version offer_version = policy.latest_supported_version(is_datagram);
+            shim_log("Offering " + offer_version.to_string());
+            Botan::TLS::Server_Information server_info(args->get_string_opt_or_else("host-name", "localhost"), port);
+            const std::vector<std::string> next_protocols = args->get_alpn_string_vec_opt("advertise-alpn");
+            chan.reset(new Botan::TLS::Client(callbacks, session_manager, creds, policy, rng,
+                                              server_info, offer_version, next_protocols));
+            }
+
+         callbacks.set_channel(chan.get());
+
+         shim_log("Starting read loop");
+
+         std::vector<uint8_t> buf(args->get_int_opt_or_else("read-size", 18*1024));
+         size_t need_to_read = 5; // header len
+
+         while(!callbacks.should_exit())
+            {
+            size_t got = socket.read(buf.data(), buf.size());
+            if(got == 0)
+               {
+               shim_log("EOF on socket");
+               break;
+               }
+
+            need_to_read = chan->received_data(buf.data(), got);
+
+            if(need_to_read == 0)
+               need_to_read = 5;
+            }
+
+         if(args->option_used("expect-total-renegotiations"))
+            {
+            const size_t exp = args->get_int_opt("expect-total-renegotiations");
+
+            if(exp != policy.sessions_established() - 1)
+               throw Shim_Exception("Unexpected number of renegotiations: saw " +
+                                    std::to_string(policy.sessions_established() - 1) +
+                                    " exp " + std::to_string(exp));
+            }
+         }
+
+      }
+   catch(Shim_Exception& e)
+      {
+      shim_exit_with_error(e.what(), e.rc());
+      }
+   catch(std::exception& e)
+      {
+      shim_exit_with_error(map_to_bogo_error(e.what()));
+      }
+   catch(...)
+      {
+      shim_exit_with_error("Unknown exception", 3);
+      }
+   return 0;
+   }
diff --git a/src/bogo_shim/config.json b/src/bogo_shim/config.json
new file mode 100644
index 000000000..57ddb04d2
--- /dev/null
+++ b/src/bogo_shim/config.json
@@ -0,0 +1,104 @@
+{
+    "LooseErrorTests": {
+        "AppDataBeforeHandshake": "BoGo expects different error before vs after CCS",
+        "AppDataBeforeHandshake-Empty": "Invalid record message",
+        "ServerHelloBogusCipher": "Unexpected error",
+        "Garbage": "Decoding error",
+        "Resume-Client-CipherMismatch": "Unexpected error",
+        "InvalidECDHPoint-Server": "Unexpected error",
+        "NoSharedCipher": "Unexpected error"
+    },
+
+    "DisabledTests": {
+        "TicketSessionIDLength-33-TLS*": "This test seems to be broken? Ask BoGo people",
+        "Renegotiate-SameClientVersion": "BoGo requires resumption use wrong version (?)",
+        "ClientAuth-Sign-Negotiate-*": "BoGo seems to be behaving badly here",
+        "Renegotiate-Server-Forbidden": "Is this restriction BoringSSL specific?",
+        "Resume-Client-Mismatch-*": "Need to investigate",
+        "Resume-Client-NoResume-TLS1-TLS11": "Need to investigate",
+        "Resume-Client-NoResume-TLS1-TLS12": "Need to investigate",
+        "Resume-Client-NoResume-TLS11-TLS12": "Need to investigate",
+
+        "*KeyUpdate*": "No TLS 1.3",
+        "*TLS13*": "No TLS 1.3",
+        "Server-JDK11*": "No TLS 1.3",
+        "*Binder*": "No TLS 1.3",
+        "PartialEncryptedExtensionsWithServerHello": "No TLS 1.3",
+        "Client-RejectJDK11DowngradeRandom": "No TLS 1.3",
+        "FragmentedClientVersion": "No TLS 1.3",
+
+        "ConflictingVersionNegotiation*": "No support for 1.3 version extension",
+        "VersionNegotiationExtension*": "No support for 1.3 version extension",
+        "IgnoreClientVersionOrder": "No support for 1.3 version extension",
+        "NoSupportedVersions": "No support for 1.3 version extension",
+
+        "DuplicateCertCompressionExt*": "No support for 1.3 cert compression extension",
+
+        "Downgrade*": "The 1.3 downgrade indicator is not implemented",
+
+        "*SSL3*": "No SSLv3",
+        "*SSLv3*": "No SSLv3",
+
+        "*NPN*": "No support for NPN",
+        "ALPNServer-Preferred-*": "No support for NPN",
+
+        "*SignedCertificateTimestamp*": "No support for SCT",
+        "*SCT*": "No support for SCT",
+
+        "*NULL-SHA*": "No support for NULL ciphers",
+        "*GREASE*": "No support for GREASE",
+        "QUICTransportParams*": "No support for QUIC",
+        "*ChannelID*": "No support for ChannelID",
+        "*TokenBinding*": "No support for Token Binding",
+        "ClientHelloPadding": "No support for client hello padding extension",
+        "TLSUnique*": "Not supported",
+        "*CECPQ2*": "Not implemented",
+        "*P-224*": "P-224 not supported in TLS",
+        "*V2ClientHello*": "No support for SSLv2 client hellos",
+        "*Ed25519*": "Ed25519 not implemented in TLS",
+        "Http*": "Stack does not have detection logic for HTTP",
+        "*FalseStart*": "Botan doesn't do false start",
+        "MaxSendFragment*": "Maximum fragment extension not supported",
+        "ExportKeyingMaterial-EmptyContext*": "No support for this",
+
+        "CheckLeafCurve": "Botan ignores this",
+
+        "OCSPStapling-Server-*": "Server doesn't support OCSP stapling currently",
+
+        "CipherNegotiation-2": "No support for cipher equivalence classes",
+        "CipherNegotiation-3": "No support for cipher equivalence classes",
+        "CipherNegotiation-4": "No support for cipher equivalence classes",
+        "CipherNegotiation-5": "No support for cipher equivalence classes",
+        "CipherNegotiation-8": "No support for cipher equivalence classes",
+
+        "ALPNServer-SelectEmpty-*": "Botan treats empty ALPN from callback as a decline",
+
+        "ServerAuth-Verify-ECDSA-P521-SHA512-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.",
+        "ServerAuth-Verify-ECDSA-SHA1-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.",
+        "ClientAuth-Verify-ECDSA-P521-SHA512-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.",
+        "ClientAuth-Verify-ECDSA-SHA1-TLS12": "BoringSSL will sign SHA-1 and SHA-512 with ECDSA but not accept them.",
+
+        "CurveTest-Client-Compressed*": "Point compression is supported, which BoGo doesn't expect",
+        "PointFormat-Client-MissingUncompressed": "Point compression is supported, which BoGo doesn't expect",
+        "CurveTest-Server-Compressed*": "Point compression is supported, which BoGo doesn't expect",
+        "PointFormat-Server-MissingUncompressed": "Point compression is supported, which BoGo doesn't expect",
+
+        "RSAPSSSupport-ConfigNoPSS-NoCerts-TLS12-Client": "Not possible to disable PSS",
+        "RSAPSSSupport-ConfigNoPSS-TLS12-Client": "Not possible to disable PSS",
+        "RSAPSSSupport-ConfigPSS-NoCerts-TLS12-Client": "Not possible to disable PSS",
+        "RSAPSSSupport-Default-NoCerts-TLS12-Client": "Not possible to disable PSS",
+        "RSAPSSSupport-ConfigNoPSS-NoCerts-TLS12-Server": "Not possible to disable PSS",
+        "RSAPSSSupport-ConfigNoPSS-TLS12-Server": "Not possible to disable PSS",
+        "RSAPSSSupport-ConfigPSS-NoCerts-TLS12-Server": "Not possible to disable PSS",
+        "RSAPSSSupport-Default-NoCerts-TLS12-Server": "Not possible to disable PSS",
+
+        "*DTLS*": "Shim needs to get support for packeted BIO",
+        "UnsolicitedServerNameAck*": "The shim always sends SNI so test doesn't work",
+
+        "Renegotiate-Client-Packed": "Packing HelloRequest with Finished loses the HelloRequest (bug)",
+        "SendHalfHelloRequest*PackHandshake": "Packing HelloRequest with Finished loses the HelloRequest (bug)",
+        "PartialClientFinishedWithClientHello": "Need to check for buffered messages when CCS (bug)",
+        "SendOCSPResponseOnResume-TLS12": "Not supported by Botan (bug)",
+        "ECDSAKeyUsage-TLS12": "Botan ignores KeyUsage (bug)"
+    }
+}
diff --git a/src/build-data/makefile.in b/src/build-data/makefile.in
index 7111c7b19..55e7a3546 100644
--- a/src/build-data/makefile.in
+++ b/src/build-data/makefile.in
@@ -27,7 +27,7 @@ SCRIPTS_DIR    = %{scripts_dir}
 INSTALLED_LIB_DIR = %{prefix}/%{libdir}
 
 # The primary target
-all: libs cli tests docs
+all: %{all_targets}
 
 # Executable targets
 CLI           = %{cli_exe}
@@ -78,7 +78,7 @@ $(TEST): $(LIBRARIES) $(TESTOBJS)
 
 FUZZERS = %{fuzzer_bin}
 
-fuzzers: libs $(FUZZERS)
+fuzzers: $(LIBRARIES) $(FUZZERS)
 
 fuzzer_corpus:
 	git clone --depth=1 https://github.com/randombit/crypto-corpus.git fuzzer_corpus
@@ -88,6 +88,16 @@ fuzzer_corpus_zip: fuzzer_corpus
 
 %{endif}
 
+%{if build_bogo_shim}
+
+bogo_shim: %{out_dir}/botan_bogo_shim
+
+# BoGo shim
+%{out_dir}/botan_bogo_shim: %{bogo_shim_src} $(LIBRARIES)
+	$(CXX) $(BUILD_FLAGS) %{include_paths} %{bogo_shim_src} $(EXE_LINKS_TO) $(LDFLAGS) %{output_to_exe}$@
+
+%{endif}
+
 # Library targets
 
 %{if build_static_lib}
diff --git a/src/lib/tls/msg_cert_req.cpp b/src/lib/tls/msg_cert_req.cpp
index 3bbdcb1f1..b6fc3825b 100644
--- a/src/lib/tls/msg_cert_req.cpp
+++ b/src/lib/tls/msg_cert_req.cpp
@@ -57,7 +57,7 @@ Certificate_Req::Certificate_Req(Handshake_IO& io,
                                  const std::vector<X509_DN>& ca_certs,
                                  Protocol_Version version) :
    m_names(ca_certs),
-   m_cert_key_types({ "RSA", "DSA", "ECDSA" })
+   m_cert_key_types({ "RSA", "ECDSA", "DSA" })
    {
    if(version.supports_negotiable_signature_algorithms())
       {
diff --git a/src/lib/tls/msg_cert_status.cpp b/src/lib/tls/msg_cert_status.cpp
index 8ad37336b..c0cd82a28 100644
--- a/src/lib/tls/msg_cert_status.cpp
+++ b/src/lib/tls/msg_cert_status.cpp
@@ -22,8 +22,8 @@ Certificate_Status::Certificate_Status(const std::vector<uint8_t>& buf)
    if(buf.size() < 5)
       throw Decoding_Error("Invalid Certificate_Status message: too small");
 
-   if(buf[0] != 1)
-      throw Decoding_Error("Unexpected Certificate_Status message: unexpected message type");
+   if(buf[0] != 1) // not OCSP
+      throw Decoding_Error("Unexpected Certificate_Status message: unexpected response type");
 
    size_t len = make_uint32(0, buf[1], buf[2], buf[3]);
 
@@ -31,33 +31,30 @@ Certificate_Status::Certificate_Status(const std::vector<uint8_t>& buf)
    if(buf.size() != len + 4)
       throw Decoding_Error("Invalid Certificate_Status: invalid length field");
 
-   m_response = std::make_shared<OCSP::Response>(buf.data() + 4, buf.size() - 4);
+   m_response.assign(buf.begin() + 4, buf.end());
    }
 
 Certificate_Status::Certificate_Status(Handshake_IO& io,
                                        Handshake_Hash& hash,
                                        std::shared_ptr<const OCSP::Response> ocsp) :
-   m_response(ocsp)
+   m_response(ocsp->raw_bits())
    {
    hash.update(io.send(*this));
    }
 
 std::vector<uint8_t> Certificate_Status::serialize() const
    {
-   BOTAN_ASSERT_NONNULL(m_response);
-   const std::vector<uint8_t>& m_resp_bits = m_response->raw_bits();
-
-   if(m_resp_bits.size() > 0xFFFFFF) // unlikely
+   if(m_response.size() > 0xFFFFFF) // unlikely
       throw Encoding_Error("OCSP response too long to encode in TLS");
 
-   const uint32_t m_resp_bits_len = static_cast<uint32_t>(m_resp_bits.size());
+   const uint32_t m_response_len = static_cast<uint32_t>(m_response.size());
 
    std::vector<uint8_t> buf;
    buf.push_back(1); // type OCSP
    for(size_t i = 1; i < 4; ++i)
-      buf[i] = get_byte(i, m_resp_bits_len);
+      buf[i] = get_byte(i, m_response_len);
 
-   buf += m_resp_bits;
+   buf += m_response;
    return buf;
    }
 
diff --git a/src/lib/tls/msg_cert_verify.cpp b/src/lib/tls/msg_cert_verify.cpp
index 230474e7a..021185003 100644
--- a/src/lib/tls/msg_cert_verify.cpp
+++ b/src/lib/tls/msg_cert_verify.cpp
@@ -51,6 +51,7 @@ Certificate_Verify::Certificate_Verify(const std::vector<uint8_t>& buf,
       }
 
    m_signature = reader.get_range<uint8_t>(2, 0, 65535);
+   reader.assert_done();
    }
 
 /*
diff --git a/src/lib/tls/msg_certificate.cpp b/src/lib/tls/msg_certificate.cpp
index a2a48ceea..1ec0766ce 100644
--- a/src/lib/tls/msg_certificate.cpp
+++ b/src/lib/tls/msg_certificate.cpp
@@ -31,7 +31,7 @@ Certificate::Certificate(Handshake_IO& io,
 /**
 * Deserialize a Certificate message
 */
-Certificate::Certificate(const std::vector<uint8_t>& buf, const Policy& /*policy_currently_unused*/)
+Certificate::Certificate(const std::vector<uint8_t>& buf, const Policy& policy)
    {
    if(buf.size() < 3)
       throw Decoding_Error("Certificate: Message malformed");
@@ -41,6 +41,10 @@ Certificate::Certificate(const std::vector<uint8_t>& buf, const Policy& /*policy
    if(total_size != buf.size() - 3)
       throw Decoding_Error("Certificate: Message malformed");
 
+   const size_t max_size = policy.maximum_certificate_chain_size();
+   if(max_size > 0 && total_size > max_size)
+      throw Decoding_Error("Certificate chain exceeds policy specified maximum size");
+
    const uint8_t* certs = buf.data() + 3;
 
    while(size_t remaining_bytes = buf.data() + buf.size() - certs)
diff --git a/src/lib/tls/msg_client_hello.cpp b/src/lib/tls/msg_client_hello.cpp
index 2d303a77e..539e2a780 100644
--- a/src/lib/tls/msg_client_hello.cpp
+++ b/src/lib/tls/msg_client_hello.cpp
@@ -92,8 +92,9 @@ Client_Hello::Client_Hello(Handshake_IO& io,
    m_suites(policy.ciphersuite_list(m_version, !client_settings.srp_identifier().empty())),
    m_comp_methods(1)
    {
-   BOTAN_ASSERT(policy.acceptable_protocol_version(client_settings.protocol_version()),
-                "Our policy accepts the version we are offering");
+   if(!policy.acceptable_protocol_version(m_version))
+      throw Internal_Error("Offering " + m_version.to_string() +
+                           " but our own policy does not accept it");
 
    /*
    * Place all empty extensions in front to avoid a bug in some systems
@@ -106,7 +107,9 @@ Client_Hello::Client_Hello(Handshake_IO& io,
       m_extensions.add(new Encrypt_then_MAC);
 
    m_extensions.add(new Renegotiation_Extension(reneg_info));
-   m_extensions.add(new Server_Name_Indicator(client_settings.hostname()));
+
+   if(client_settings.hostname() != "")
+      m_extensions.add(new Server_Name_Indicator(client_settings.hostname()));
 
    if(policy.support_cert_status_message())
       m_extensions.add(new Certificate_Status_Request({}, {}));
@@ -163,6 +166,10 @@ Client_Hello::Client_Hello(Handshake_IO& io,
    m_suites(policy.ciphersuite_list(m_version, (session.srp_identifier() != ""))),
    m_comp_methods(1)
    {
+   if(!policy.acceptable_protocol_version(m_version))
+      throw Internal_Error("Offering " + m_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());
 
@@ -273,7 +280,7 @@ Client_Hello::Client_Hello(const std::vector<uint8_t>& buf)
 
    m_comp_methods = reader.get_range_vector<uint8_t>(1, 1, 255);
 
-   m_extensions.deserialize(reader);
+   m_extensions.deserialize(reader, Connection_Side::SERVER);
 
    if(offered_suite(static_cast<uint16_t>(TLS_EMPTY_RENEGOTIATION_INFO_SCSV)))
       {
diff --git a/src/lib/tls/msg_client_kex.cpp b/src/lib/tls/msg_client_kex.cpp
index b3dff072e..f55568c8e 100644
--- a/src/lib/tls/msg_client_kex.cpp
+++ b/src/lib/tls/msg_client_kex.cpp
@@ -256,6 +256,7 @@ Client_Key_Exchange::Client_Key_Exchange(const std::vector<uint8_t>& contents,
 
       TLS_Data_Reader reader("ClientKeyExchange", contents);
       const std::vector<uint8_t> encrypted_pre_master = reader.get_range<uint8_t>(2, 0, 65535);
+      reader.assert_done();
 
       PK_Decryptor_EME decryptor(*server_rsa_kex_key, rng, "PKCS1v15");
 
@@ -386,6 +387,8 @@ Client_Key_Exchange::Client_Key_Exchange(const std::vector<uint8_t>& contents,
             */
             m_pre_master = rng.random_vec(ka_key->public_value().size());
             }
+
+         reader.assert_done();
          }
       else
          throw Internal_Error("Client_Key_Exchange: Unknown key exchange negotiated");
diff --git a/src/lib/tls/msg_server_hello.cpp b/src/lib/tls/msg_server_hello.cpp
index 223bddde5..b4c47f516 100644
--- a/src/lib/tls/msg_server_hello.cpp
+++ b/src/lib/tls/msg_server_hello.cpp
@@ -163,7 +163,7 @@ Server_Hello::Server_Hello(const std::vector<uint8_t>& buf)
 
    m_comp_method = reader.get_byte();
 
-   m_extensions.deserialize(reader);
+   m_extensions.deserialize(reader, Connection_Side::CLIENT);
    }
 
 /*
diff --git a/src/lib/tls/msg_session_ticket.cpp b/src/lib/tls/msg_session_ticket.cpp
index b9bf7e94d..bd0d74ceb 100644
--- a/src/lib/tls/msg_session_ticket.cpp
+++ b/src/lib/tls/msg_session_ticket.cpp
@@ -40,6 +40,7 @@ New_Session_Ticket::New_Session_Ticket(const std::vector<uint8_t>& buf)
 
    m_ticket_lifetime_hint = reader.get_uint32_t();
    m_ticket = reader.get_range<uint8_t>(2, 0, 65535);
+   reader.assert_done();
    }
 
 std::vector<uint8_t> New_Session_Ticket::serialize() const
diff --git a/src/lib/tls/tls_alert.cpp b/src/lib/tls/tls_alert.cpp
index e1e8c6eb6..60c9c4b98 100644
--- a/src/lib/tls/tls_alert.cpp
+++ b/src/lib/tls/tls_alert.cpp
@@ -6,7 +6,7 @@
 */
 
 #include <botan/tls_alert.h>
-#include <botan/exceptn.h>
+#include <botan/tls_exceptn.h>
 
 namespace Botan {
 
@@ -15,13 +15,13 @@ namespace TLS {
 Alert::Alert(const secure_vector<uint8_t>& buf)
    {
    if(buf.size() != 2)
-      throw Decoding_Error("Alert: Bad size " + std::to_string(buf.size()) +
-                           " for alert message");
+      throw Decoding_Error("Bad size (" + std::to_string(buf.size()) +
+                           ") for TLS alert message");
 
    if(buf[0] == 1)      m_fatal = false;
    else if(buf[0] == 2) m_fatal = true;
    else
-      throw Decoding_Error("Alert: Bad code for alert level");
+      throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Bad code for TLS alert level");
 
    const uint8_t dc = buf[1];
 
@@ -103,6 +103,8 @@ std::string Alert::type_string() const
          return "bad_certificate_hash_value";
       case UNKNOWN_PSK_IDENTITY:
          return "unknown_psk_identity";
+      case CERTIFICATE_REQUIRED:
+         return "certificate_required";
       case NO_APPLICATION_PROTOCOL:
          return "no_application_protocol";
 
diff --git a/src/lib/tls/tls_alert.h b/src/lib/tls/tls_alert.h
index 89530e238..d9d3fe313 100644
--- a/src/lib/tls/tls_alert.h
+++ b/src/lib/tls/tls_alert.h
@@ -56,6 +56,7 @@ class BOTAN_PUBLIC_API(2,0) Alert final
          BAD_CERTIFICATE_STATUS_RESPONSE = 113,
          BAD_CERTIFICATE_HASH_VALUE      = 114,
          UNKNOWN_PSK_IDENTITY            = 115,
+         CERTIFICATE_REQUIRED            = 116, // RFC 8446
 
          NO_APPLICATION_PROTOCOL         = 120, // RFC 7301
 
diff --git a/src/lib/tls/tls_algos.h b/src/lib/tls/tls_algos.h
index 19612be2e..4852a4349 100644
--- a/src/lib/tls/tls_algos.h
+++ b/src/lib/tls/tls_algos.h
@@ -109,10 +109,10 @@ enum class Signature_Scheme : uint16_t {
 
 BOTAN_UNSTABLE_API const std::vector<Signature_Scheme>& all_signature_schemes();
 
-bool signature_scheme_is_known(Signature_Scheme scheme);
+bool BOTAN_UNSTABLE_API signature_scheme_is_known(Signature_Scheme scheme);
 std::string BOTAN_UNSTABLE_API sig_scheme_to_string(Signature_Scheme scheme);
-std::string hash_function_of_scheme(Signature_Scheme scheme);
-std::string padding_string_for_scheme(Signature_Scheme scheme);
+std::string BOTAN_UNSTABLE_API hash_function_of_scheme(Signature_Scheme scheme);
+std::string BOTAN_UNSTABLE_API padding_string_for_scheme(Signature_Scheme scheme);
 std::string signature_algorithm_of_scheme(Signature_Scheme scheme);
 
 /*
diff --git a/src/lib/tls/tls_channel.cpp b/src/lib/tls/tls_channel.cpp
index e021be518..0ee77c83c 100644
--- a/src/lib/tls/tls_channel.cpp
+++ b/src/lib/tls/tls_channel.cpp
@@ -187,8 +187,13 @@ void Channel::renegotiate(bool force_full_renegotiation)
       return;
 
    if(auto active = active_state())
+      {
+      if(force_full_renegotiation == false)
+         force_full_renegotiation = !policy().allow_resumption_for_renegotiation();
+
       initiate_handshake(create_handshake_state(active->version()),
                          force_full_renegotiation);
+      }
    else
       throw Invalid_State("Cannot renegotiate on inactive connection");
    }
@@ -334,12 +339,41 @@ size_t Channel::received_data(const uint8_t input[], size_t input_size)
             throw TLS_Exception(Alert::RECORD_OVERFLOW,
                                 "TLS plaintext record is larger than allowed maximum");
 
+         if(auto pending = pending_state())
+            {
+            if(pending->server_hello() != nullptr && record_version != pending->version())
+               {
+               throw TLS_Exception(Alert::PROTOCOL_VERSION,
+                                   "Received unexpected record version");
+               }
+            }
+         else if(auto active = active_state())
+            {
+            if(record_version != active->version())
+               {
+               throw TLS_Exception(Alert::PROTOCOL_VERSION,
+                                   "Received unexpected record version");
+               }
+            }
+         else
+            {
+            // For initial records just check for basic sanity
+            if(record_version.major_version() != 3 &&
+               record_version.major_version() != 0xFE)
+               {
+               throw TLS_Exception(Alert::PROTOCOL_VERSION,
+                                   "Received unexpected record version in initial record");
+               }
+            }
+
          if(record_type == HANDSHAKE || record_type == CHANGE_CIPHER_SPEC)
             {
             process_handshake_ccs(record_data, record_sequence, record_type, record_version);
             }
          else if(record_type == APPLICATION_DATA)
             {
+            if(pending_state() != nullptr)
+               throw TLS_Exception(Alert::UNEXPECTED_MESSAGE, "Can't interleave application and handshake data");
             process_application_data(record_sequence, record_data);
             }
          else if(record_type == ALERT)
@@ -444,13 +478,7 @@ void Channel::process_application_data(uint64_t seq_no, const secure_vector<uint
    if(!active_state())
       throw Unexpected_Message("Application data before handshake done");
 
-   /*
-   * OpenSSL among others sends empty records in versions
-   * before TLS v1.1 in order to randomize the IV of the
-   * following record. Avoid spurious callbacks.
-   */
-   if(record.size() > 0)
-      callbacks().tls_record_received(seq_no, record.data(), record.size());
+   callbacks().tls_record_received(seq_no, record.data(), record.size());
    }
 
 void Channel::process_alert(const secure_vector<uint8_t>& record)
@@ -483,7 +511,7 @@ void Channel::write_record(Connection_Cipher_State* cipher_state, uint16_t epoch
    {
    BOTAN_ASSERT(m_pending_state || m_active_state, "Some connection state exists");
 
-   Protocol_Version record_version =
+   const Protocol_Version record_version =
       (m_pending_state) ? (m_pending_state->version()) : (m_active_state->version());
 
    Record_Message record_message(record_type, 0, input, length);
@@ -520,18 +548,29 @@ void Channel::send_record_array(uint16_t epoch, uint8_t type, const uint8_t inpu
 
    if(type == APPLICATION_DATA && m_active_state->version().supports_explicit_cbc_ivs() == false)
       {
-      write_record(cipher_state.get(), epoch, type, input, 1);
-      input += 1;
-      length -= 1;
-      }
+      while(length)
+         {
+         write_record(cipher_state.get(), epoch, type, input, 1);
+         input += 1;
+         length -= 1;
 
-   while(length)
+         const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE);
+         write_record(cipher_state.get(), epoch, type, input, sending);
+
+         input += sending;
+         length -= sending;
+         }
+      }
+   else
       {
-      const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE);
-      write_record(cipher_state.get(), epoch, type, input, sending);
+      while(length)
+         {
+         const size_t sending = std::min<size_t>(length, MAX_PLAINTEXT_SIZE);
+         write_record(cipher_state.get(), epoch, type, input, sending);
 
-      input += sending;
-      length -= sending;
+         input += sending;
+         length -= sending;
+         }
       }
    }
 
diff --git a/src/lib/tls/tls_channel.h b/src/lib/tls/tls_channel.h
index 0362faaa8..63cbcf0fc 100644
--- a/src/lib/tls/tls_channel.h
+++ b/src/lib/tls/tls_channel.h
@@ -198,6 +198,8 @@ class BOTAN_PUBLIC_API(2,0) Channel
       */
       bool timeout_check();
 
+      virtual std::string application_protocol() const = 0;
+
    protected:
 
       virtual void process_handshake_msg(const Handshake_State* active_state,
diff --git a/src/lib/tls/tls_ciphersuite.cpp b/src/lib/tls/tls_ciphersuite.cpp
index b8a7e70d7..88837387e 100644
--- a/src/lib/tls/tls_ciphersuite.cpp
+++ b/src/lib/tls/tls_ciphersuite.cpp
@@ -57,6 +57,18 @@ bool Ciphersuite::ecc_ciphersuite() const
           auth_method() == Auth_Method::ECDSA;
    }
 
+bool Ciphersuite::usable_in_version(Protocol_Version version) const
+   {
+   if(!version.supports_aead_modes())
+      {
+      // Old versions do not support AEAD, or any MAC but SHA-1
+      if(mac_algo() != "SHA-1")
+         return false;
+      }
+
+   return true;
+   }
+
 bool Ciphersuite::cbc_ciphersuite() const
    {
    return (mac_algo() != "AEAD");
@@ -81,6 +93,19 @@ Ciphersuite Ciphersuite::by_id(uint16_t suite)
    return Ciphersuite(); // some unknown ciphersuite
    }
 
+Ciphersuite Ciphersuite::from_name(const std::string& name)
+   {
+   const std::vector<Ciphersuite>& all_suites = all_known_ciphersuites();
+
+   for(auto suite : all_suites)
+      {
+      if(suite.to_string() == name)
+         return suite;
+      }
+
+   return Ciphersuite(); // some unknown ciphersuite
+   }
+
 namespace {
 
 bool have_hash(const std::string& prf)
diff --git a/src/lib/tls/tls_ciphersuite.h b/src/lib/tls/tls_ciphersuite.h
index 2ee3df20e..7ef7623bb 100644
--- a/src/lib/tls/tls_ciphersuite.h
+++ b/src/lib/tls/tls_ciphersuite.h
@@ -10,6 +10,7 @@
 
 #include <botan/types.h>
 #include <botan/tls_algos.h>
+#include <botan/tls_version.h>
 #include <string>
 #include <vector>
 
@@ -30,6 +31,13 @@ class BOTAN_PUBLIC_API(2,0) Ciphersuite final
       */
       static Ciphersuite by_id(uint16_t suite);
 
+      /**
+      * Convert an SSL/TLS ciphersuite name to algorithm fields
+      * @param name the IANA name for the desired ciphersuite
+      * @return ciphersuite object
+      */
+      static Ciphersuite from_name(const std::string& name);
+
       /**
       * Returns true iff this suite is a known SCSV
       */
@@ -115,6 +123,8 @@ class BOTAN_PUBLIC_API(2,0) Ciphersuite final
       */
       bool valid() const { return m_usable; }
 
+      bool usable_in_version(Protocol_Version version) const;
+
       bool operator<(const Ciphersuite& o) const { return ciphersuite_code() < o.ciphersuite_code(); }
       bool operator<(const uint16_t c) const { return ciphersuite_code() < c; }
 
diff --git a/src/lib/tls/tls_client.cpp b/src/lib/tls/tls_client.cpp
index 94616c60b..eb6d21b14 100644
--- a/src/lib/tls/tls_client.cpp
+++ b/src/lib/tls/tls_client.cpp
@@ -23,9 +23,10 @@ namespace {
 class Client_Handshake_State final : public Handshake_State
    {
    public:
-      // using Handshake_State::Handshake_State;
-
-      Client_Handshake_State(Handshake_IO* io, Callbacks& cb) : Handshake_State(io, cb) {}
+      Client_Handshake_State(Handshake_IO* io, Callbacks& cb) :
+         Handshake_State(io, cb),
+         m_is_reneg(false)
+         {}
 
       const Public_Key& get_server_public_key() const
          {
@@ -33,13 +34,26 @@ class Client_Handshake_State final : public Handshake_State
          return *server_public_key.get();
          }
 
-      bool is_a_resumption() const { return (resume_master_secret.empty() == false); }
+      bool is_a_resumption() const { return (resumed_session != nullptr); }
 
-      std::unique_ptr<Public_Key> server_public_key;
+      bool is_a_renegotiation() const { return m_is_reneg; }
+
+      const secure_vector<uint8_t>& resume_master_secret() const
+         {
+         BOTAN_STATE_CHECK(is_a_resumption());
+         return resumed_session->master_secret();
+         }
+
+      const std::vector<X509_Certificate>& resume_peer_certs() const
+         {
+         BOTAN_STATE_CHECK(is_a_resumption());
+         return resumed_session->peer_certs();
+         }
 
+      std::unique_ptr<Public_Key> server_public_key;
       // Used during session resumption
-      secure_vector<uint8_t> resume_master_secret;
-      std::vector<X509_Certificate> resume_peer_certs;
+      std::unique_ptr<Session> resumed_session;
+      bool m_is_reneg = false;
    };
 
 }
@@ -123,8 +137,9 @@ std::vector<X509_Certificate>
 Client::get_peer_cert_chain(const Handshake_State& state) const
    {
    const Client_Handshake_State& cstate = dynamic_cast<const Client_Handshake_State&>(state);
-   if(cstate.resume_peer_certs.size() > 0)
-      return cstate.resume_peer_certs;
+
+   if(cstate.is_a_resumption())
+      return cstate.resume_peer_certs();
 
    if(state.server_certs())
       return state.server_certs()->cert_chain();
@@ -137,7 +152,8 @@ Client::get_peer_cert_chain(const Handshake_State& state) const
 void Client::initiate_handshake(Handshake_State& state,
                                 bool force_full_renegotiation)
    {
-   send_client_hello(state, force_full_renegotiation, state.version());
+   send_client_hello(state, force_full_renegotiation,
+                     policy().latest_supported_version(state.version().is_datagram_protocol()));
    }
 
 void Client::send_client_hello(Handshake_State& state_base,
@@ -154,16 +170,23 @@ void Client::send_client_hello(Handshake_State& state_base,
 
    if(!force_full_renegotiation && !m_info.empty())
       {
-      Session session_info;
-      if(session_manager().load_from_server_info(m_info, session_info))
+      std::unique_ptr<Session> session_info(new Session);;
+      if(session_manager().load_from_server_info(m_info, *session_info))
          {
          /*
-         Ensure that the session protocol type matches what we want to use
+         Ensure that the session protocol cipher and version are acceptable
          If not skip the resume and establish a new session
          */
-         if(version == session_info.version() && policy().acceptable_ciphersuite(session_info.ciphersuite()))
+         const bool exact_version = session_info->version() == version;
+         const bool ok_version =
+            (session_info->version().is_datagram_protocol() == version.is_datagram_protocol()) &&
+            policy().acceptable_protocol_version(session_info->version());
+
+         const bool session_version_ok = policy().only_resume_with_exact_version() ? exact_version : ok_version;
+
+         if(policy().acceptable_ciphersuite(session_info->ciphersuite()) && session_version_ok)
             {
-            if(srp_identifier == "" || session_info.srp_identifier() == srp_identifier)
+            if(srp_identifier == "" || session_info->srp_identifier() == srp_identifier)
                {
                state.client_hello(
                   new Client_Hello(state.handshake_io(),
@@ -172,11 +195,10 @@ void Client::send_client_hello(Handshake_State& state_base,
                                    callbacks(),
                                    rng(),
                                    secure_renegotiation_data_for_client_hello(),
-                                   session_info,
+                                   *session_info,
                                    next_protocols));
 
-               state.resume_master_secret = session_info.master_secret();
-               state.resume_peer_certs = session_info.peer_certs();
+               state.resumed_session = std::move(session_info);
                }
             }
          }
@@ -215,19 +237,33 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
 
       // Ignore request entirely if we are currently negotiating a handshake
       if(state.client_hello())
-         return;
+         {
+         throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Cannot renegotiate during a handshake");
+         }
 
       if(policy().allow_server_initiated_renegotiation())
          {
-         if(!secure_renegotiation_supported() && policy().allow_insecure_renegotiation() == false)
-            send_warning_alert(Alert::NO_RENEGOTIATION);
+         if(secure_renegotiation_supported() || policy().allow_insecure_renegotiation())
+            {
+            state.m_is_reneg = true;
+            this->initiate_handshake(state, true);
+            }
          else
-            this->initiate_handshake(state, false);
+            {
+            throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Client policy prohibits insecure renegotiation");
+            }
          }
       else
          {
-         // RFC 5746 section 4.2
-         send_warning_alert(Alert::NO_RENEGOTIATION);
+         if(policy().abort_connection_on_undesired_renegotiation())
+            {
+            throw TLS_Exception(Alert::NO_RENEGOTIATION, "Client policy prohibits renegotiation");
+            }
+         else
+            {
+            // RFC 5746 section 4.2
+            send_warning_alert(Alert::NO_RENEGOTIATION);
+            }
          }
 
       return;
@@ -257,6 +293,12 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
                              "Server replied with ciphersuite we didn't send");
          }
 
+      if(!Ciphersuite::by_id(state.server_hello()->ciphersuite()).usable_in_version(state.server_hello()->version()))
+         {
+         throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+                             "Server replied using a ciphersuite not allowed in version it offered");
+         }
+
       if(Ciphersuite::is_scsv(state.server_hello()->ciphersuite()))
          {
          throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
@@ -265,7 +307,7 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
 
       if(state.server_hello()->compression_method() != 0)
          {
-         throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+         throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
                              "Server replied with non-null compression method");
          }
 
@@ -283,10 +325,10 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
          // Server sent us back an extension we did not send!
 
          std::ostringstream msg;
-         msg << "Server replied with " << diff.size() << " unsupported extensions:";
+         msg << "Server replied with unsupported extensions:";
          for(auto&& d : diff)
             msg << " " << static_cast<int>(d);
-         throw TLS_Exception(Alert::HANDSHAKE_FAILURE, msg.str());
+         throw TLS_Exception(Alert::UNSUPPORTED_EXTENSION, msg.str());
          }
 
       if(uint16_t srtp = state.server_hello()->srtp_profile())
@@ -319,10 +361,26 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
             throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
                                 "Server resumed session but with wrong version");
 
-         state.compute_session_keys(state.resume_master_secret);
+         if(state.server_hello()->supports_extended_master_secret() &&
+            !state.resumed_session->supports_extended_master_secret())
+            {
+            throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+                                "Server resumed session but added extended master secret");
+            }
+
+         if(!state.server_hello()->supports_extended_master_secret() &&
+            state.resumed_session->supports_extended_master_secret())
+            {
+            throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+                                "Server resumed session and removed extended master secret");
+            }
+
+         state.compute_session_keys(state.resume_master_secret());
 
          if(state.server_hello()->supports_session_ticket())
+            {
             state.set_expected_next(NEW_SESSION_TICKET);
+            }
          else
             {
             state.set_expected_next(HANDSHAKE_CCS);
@@ -332,8 +390,25 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
          {
          // new session
 
-         state.resume_master_secret.clear();
-         state.resume_peer_certs.clear();
+         if(active_state)
+            {
+            // Here we are testing things that should not change during a renegotation,
+            // even if the server creates a new session. Howerver they might change
+            // in a resumption scenario.
+
+            if(active_state->version() != state.server_hello()->version())
+               throw TLS_Exception(Alert::PROTOCOL_VERSION,
+                                   "Server changed version after renegotiation");
+
+            if(state.server_hello()->supports_extended_master_secret() !=
+               active_state->server_hello()->supports_extended_master_secret())
+               {
+               throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+                                   "Server changed its mind about extended master secret");
+               }
+            }
+
+         state.resumed_session.reset(); // non-null if we were attempting a resumption
 
          if(state.client_hello()->version().is_datagram_protocol() !=
             state.server_hello()->version().is_datagram_protocol())
@@ -372,7 +447,7 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
                depending on if it has an identity hint for us.
 
                (EC)DHE_PSK always sends a server key exchange for the
-               DH exchange portion.
+               DH exchange portion, and is covered by block below
             */
 
             state.set_expected_next(SERVER_KEX);
@@ -406,7 +481,17 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
       in case an OCSP response was also available
       */
 
-      std::unique_ptr<Public_Key> peer_key(server_certs[0].subject_public_key());
+      X509_Certificate server_cert = server_certs[0];
+
+      if(active_state && active_state->server_certs())
+         {
+         X509_Certificate current_cert = active_state->server_certs()->cert_chain().at(0);
+
+         if(current_cert != server_cert)
+            throw TLS_Exception(Alert::BAD_CERTIFICATE, "Server certificate changed during renegotiation");
+         }
+
+      std::unique_ptr<Public_Key> peer_key(server_cert.subject_public_key());
 
       const std::string expected_key_type =
          state.ciphersuite().signature_used() ? state.ciphersuite().sig_algo() : "RSA";
@@ -444,9 +529,13 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
                                               m_info.hostname(),
                                               policy());
             }
+         catch(TLS_Exception& e)
+            {
+            throw;
+            }
          catch(std::exception& e)
             {
-            throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what());
+            throw TLS_Exception(Alert::INTERNAL_ERROR, e.what());
             }
          }
       }
@@ -466,7 +555,8 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
       }
    else if(type == SERVER_KEX)
       {
-      state.set_expected_next(CERTIFICATE_REQUEST); // optional
+      if(state.ciphersuite().psk_ciphersuite() == false)
+         state.set_expected_next(CERTIFICATE_REQUEST); // optional
       state.set_expected_next(SERVER_HELLO_DONE);
 
       state.server_kex(
@@ -505,7 +595,15 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
 
             std::vector<std::shared_ptr<const OCSP::Response>> ocsp;
             if(state.server_cert_status() != nullptr)
-               ocsp.push_back(state.server_cert_status()->response());
+               {
+               try {
+                   ocsp.push_back(std::make_shared<OCSP::Response>(state.server_cert_status()->response()));
+               }
+               catch(Decoding_Error&)
+                  {
+                  // ignore it here because it might be our fault
+                  }
+               }
 
             callbacks().tls_verify_cert_chain(state.server_certs()->cert_chain(),
                                               ocsp,
@@ -514,9 +612,13 @@ void Client::process_handshake_msg(const Handshake_State* active_state,
                                               m_info.hostname(),
                                               policy());
             }
+         catch(TLS_Exception& e)
+            {
+            throw;
+            }
          catch(std::exception& e)
             {
-            throw TLS_Exception(Alert::BAD_CERTIFICATE, e.what());
+            throw TLS_Exception(Alert::INTERNAL_ERROR, e.what());
             }
          }
 
diff --git a/src/lib/tls/tls_client.h b/src/lib/tls/tls_client.h
index 63c26b9cd..005370e78 100644
--- a/src/lib/tls/tls_client.h
+++ b/src/lib/tls/tls_client.h
@@ -132,7 +132,7 @@ class BOTAN_PUBLIC_API(2,0) Client final : public Channel
       /**
       * @return network protocol as advertised by the TLS server, if server sent the ALPN extension
       */
-      const std::string& application_protocol() const { return m_application_protocol; }
+      std::string application_protocol() const override { return m_application_protocol; }
    private:
       void init(const Protocol_Version& protocol_version,
                 const std::vector<std::string>& next_protocols);
diff --git a/src/lib/tls/tls_extensions.cpp b/src/lib/tls/tls_extensions.cpp
index c5e6f2831..a673f867b 100644
--- a/src/lib/tls/tls_extensions.cpp
+++ b/src/lib/tls/tls_extensions.cpp
@@ -16,8 +16,10 @@ namespace TLS {
 
 namespace {
 
-Extension* make_extension(TLS_Data_Reader& reader, uint16_t code, uint16_t size)
+Extension* make_extension(TLS_Data_Reader& reader, uint16_t code, uint16_t size, Connection_Side side)
    {
+   BOTAN_UNUSED(side);
+
    switch(code)
       {
       case TLSEXT_SERVER_NAME_INDICATION:
@@ -65,7 +67,7 @@ Extension* make_extension(TLS_Data_Reader& reader, uint16_t code, uint16_t size)
 
 }
 
-void Extensions::deserialize(TLS_Data_Reader& reader)
+void Extensions::deserialize(TLS_Data_Reader& reader, Connection_Side side)
    {
    if(reader.has_remaining())
       {
@@ -79,9 +81,14 @@ void Extensions::deserialize(TLS_Data_Reader& reader)
          const uint16_t extension_code = reader.get_uint16_t();
          const uint16_t extension_size = reader.get_uint16_t();
 
-         Extension* extn = make_extension(reader,
-                                          extension_code,
-                                          extension_size);
+         const auto type = static_cast<Handshake_Extension_Type>(extension_code);
+
+         if(m_extensions.find(type) != m_extensions.end())
+            throw TLS_Exception(TLS::Alert::DECODE_ERROR,
+                                "Peer sent duplicated extensions");
+
+         Extension* extn = make_extension(
+            reader, extension_code, extension_size, side);
 
          this->add(extn);
          }
@@ -259,6 +266,9 @@ Application_Layer_Protocol_Notification::Application_Layer_Protocol_Notification
       if(bytes_remaining < p.size() + 1)
          throw Decoding_Error("Bad encoding of ALPN, length field too long");
 
+      if(p.empty())
+         throw Decoding_Error("Empty ALPN protocol not allowed");
+
       bytes_remaining -= (p.size() + 1);
 
       m_protocols.push_back(p);
diff --git a/src/lib/tls/tls_extensions.h b/src/lib/tls/tls_extensions.h
index 98856b951..3ecfb7c0f 100644
--- a/src/lib/tls/tls_extensions.h
+++ b/src/lib/tls/tls_extensions.h
@@ -1,6 +1,6 @@
 /*
 * TLS Extensions
-* (C) 2011,2012,2016,2018 Jack Lloyd
+* (C) 2011,2012,2016,2018,2019 Jack Lloyd
 * (C) 2016 Juraj Somorovsky
 * (C) 2016 Matthias Gierlings
 *
@@ -11,6 +11,7 @@
 #define BOTAN_TLS_EXTENSIONS_H_
 
 #include <botan/tls_algos.h>
+#include <botan/tls_magic.h>
 #include <botan/secmem.h>
 #include <botan/x509_dn.h>
 #include <vector>
@@ -471,7 +472,7 @@ class BOTAN_UNSTABLE_API Extensions final
 
       std::vector<uint8_t> serialize() const;
 
-      void deserialize(TLS_Data_Reader& reader);
+      void deserialize(TLS_Data_Reader& reader, Connection_Side side);
 
       /**
       * Remvoe an extension from this extensions object, if it exists.
@@ -482,7 +483,10 @@ class BOTAN_UNSTABLE_API Extensions final
 
       Extensions() = default;
 
-      explicit Extensions(TLS_Data_Reader& reader) { deserialize(reader); }
+      Extensions(TLS_Data_Reader& reader, Connection_Side side)
+         {
+         deserialize(reader, side);
+         }
 
    private:
       Extensions(const Extensions&) = delete;
diff --git a/src/lib/tls/tls_handshake_state.cpp b/src/lib/tls/tls_handshake_state.cpp
index a9c7514c1..8bc603a43 100644
--- a/src/lib/tls/tls_handshake_state.cpp
+++ b/src/lib/tls/tls_handshake_state.cpp
@@ -138,7 +138,7 @@ uint32_t bitmask_for_handshake_type(Handshake_Type type)
                        "Unknown TLS handshake message type " + std::to_string(type));
    }
 
-std::string handshake_mask_to_string(uint32_t mask)
+std::string handshake_mask_to_string(uint32_t mask, char combiner)
    {
    const Handshake_Type types[] = {
       HELLO_VERIFY_REQUEST,
@@ -165,7 +165,7 @@ std::string handshake_mask_to_string(uint32_t mask)
       if(mask & bitmask_for_handshake_type(t))
          {
          if(!empty)
-            o << ",";
+            o << combiner;
          o << handshake_type_to_string(t);
          empty = false;
          }
@@ -304,10 +304,9 @@ void Handshake_State::confirm_transition_to(Handshake_Type handshake_msg)
    const bool ok = (m_hand_expecting_mask & mask) != 0; // overlap?
 
    if(!ok)
-      throw Unexpected_Message("Unexpected state transition in handshake, got type " +
-                               std::to_string(handshake_msg) +
-                               " expected " + handshake_mask_to_string(m_hand_expecting_mask) +
-                               " received " + handshake_mask_to_string(m_hand_received_mask));
+      throw Unexpected_Message("Unexpected state transition in handshake, expected " +
+                               handshake_mask_to_string(m_hand_expecting_mask, '|') +
+                               " received " + handshake_mask_to_string(m_hand_received_mask, '+'));
 
    /* We don't know what to expect next, so force a call to
       set_expected_next; if it doesn't happen, the next transition
@@ -385,18 +384,18 @@ Handshake_State::choose_sig_format(const Private_Key& key,
       {
       const std::vector<Signature_Scheme> allowed = policy.allowed_signature_schemes();
 
-      std::vector<Signature_Scheme> schemes =
+      std::vector<Signature_Scheme> requested =
          (for_client_auth) ? cert_req()->signature_schemes() : client_hello()->signature_schemes();
 
-      if(schemes.empty())
+      if(requested.empty())
          {
          // Implicit SHA-1
-         schemes.push_back(Signature_Scheme::RSA_PKCS1_SHA1);
-         schemes.push_back(Signature_Scheme::ECDSA_SHA1);
-         schemes.push_back(Signature_Scheme::DSA_SHA1);
+         requested.push_back(Signature_Scheme::RSA_PKCS1_SHA1);
+         requested.push_back(Signature_Scheme::ECDSA_SHA1);
+         requested.push_back(Signature_Scheme::DSA_SHA1);
          }
 
-      for(Signature_Scheme scheme : schemes)
+      for(Signature_Scheme scheme : allowed)
          {
          if(signature_scheme_is_known(scheme) == false)
             {
@@ -405,7 +404,7 @@ Handshake_State::choose_sig_format(const Private_Key& key,
 
          if(signature_algorithm_of_scheme(scheme) == sig_algo)
             {
-            if(std::find(allowed.begin(), allowed.end(), scheme) != allowed.end())
+            if(std::find(requested.begin(), requested.end(), scheme) != requested.end())
                {
                chosen_scheme = scheme;
                break;
@@ -528,11 +527,15 @@ Handshake_State::parse_sig_format(const Public_Key& key,
       for_client_auth ? cert_req()->signature_schemes() :
       client_hello()->signature_schemes();
 
+   if(!signature_scheme_is_known(scheme))
+      throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+                          "Peer sent unknown signature scheme");
+
    const std::string hash_algo = hash_function_of_scheme(scheme);
 
    if(!supported_algos_include(supported_algos, key_type, hash_algo))
       {
-      throw TLS_Exception(Alert::HANDSHAKE_FAILURE,
+      throw TLS_Exception(Alert::ILLEGAL_PARAMETER,
                           "TLS signature extension did not allow for " +
                           key_type + "/" + hash_algo + " signature");
       }
diff --git a/src/lib/tls/tls_messages.h b/src/lib/tls/tls_messages.h
index 98c46dfa8..0549a66a6 100644
--- a/src/lib/tls/tls_messages.h
+++ b/src/lib/tls/tls_messages.h
@@ -98,6 +98,8 @@ class BOTAN_UNSTABLE_API Client_Hello final : public Handshake_Message
 
       const std::vector<uint8_t>& session_id() const { return m_session_id; }
 
+      const std::vector<uint8_t>& compression_methods() const { return m_comp_methods; }
+
       const std::vector<uint16_t>& ciphersuites() const { return m_suites; }
 
       bool offered_suite(uint16_t ciphersuite) const;
@@ -387,7 +389,9 @@ class BOTAN_UNSTABLE_API Certificate_Status final : public Handshake_Message
    public:
       Handshake_Type type() const override { return CERTIFICATE_STATUS; }
 
-      std::shared_ptr<const OCSP::Response> response() const { return m_response; }
+      //std::shared_ptr<const OCSP::Response> response() const { return m_response; }
+
+      const std::vector<uint8_t>& response() const { return m_response; }
 
       Certificate_Status(const std::vector<uint8_t>& buf);
 
@@ -397,7 +401,7 @@ class BOTAN_UNSTABLE_API Certificate_Status final : public Handshake_Message
 
    private:
       std::vector<uint8_t> serialize() const override;
-      std::shared_ptr<const OCSP::Response> m_response;
+      std::vector<uint8_t> m_response;
    };
 
 /**
diff --git a/src/lib/tls/tls_policy.cpp b/src/lib/tls/tls_policy.cpp
index 4caaf623a..58ba73ade 100644
--- a/src/lib/tls/tls_policy.cpp
+++ b/src/lib/tls/tls_policy.cpp
@@ -25,6 +25,8 @@ std::vector<Signature_Scheme> Policy::allowed_signature_schemes() const
 
    for(Signature_Scheme scheme : all_signature_schemes())
       {
+      if(signature_scheme_is_known(scheme) == false)
+         continue;
       const bool sig_allowed = allowed_signature_method(signature_algorithm_of_scheme(scheme));
       const bool hash_allowed = allowed_signature_hash(hash_function_of_scheme(scheme));
 
@@ -57,7 +59,7 @@ std::vector<std::string> Policy::allowed_ciphers() const
       //"AES-128",
       //"Camellia-256",
       //"Camellia-128",
-      //"SEED"
+      //"SEED",
       //"3DES",
       };
    }
@@ -292,19 +294,19 @@ Protocol_Version Policy::latest_supported_version(bool datagram) const
    {
    if(datagram)
       {
-      if(allow_dtls12())
+      if(acceptable_protocol_version(Protocol_Version::DTLS_V12))
          return Protocol_Version::DTLS_V12;
-      if(allow_dtls10())
+      if(acceptable_protocol_version(Protocol_Version::DTLS_V10))
          return Protocol_Version::DTLS_V10;
       throw Invalid_State("Policy forbids all available DTLS version");
       }
    else
       {
-      if(allow_tls12())
+      if(acceptable_protocol_version(Protocol_Version::TLS_V12))
          return Protocol_Version::TLS_V12;
-      if(allow_tls11())
+      if(acceptable_protocol_version(Protocol_Version::TLS_V11))
          return Protocol_Version::TLS_V11;
-      if(allow_tls10())
+      if(acceptable_protocol_version(Protocol_Version::TLS_V10))
          return Protocol_Version::TLS_V10;
       throw Invalid_State("Policy forbids all available TLS version");
       }
@@ -329,6 +331,13 @@ bool Policy::hide_unknown_users() const { return false; }
 bool Policy::server_uses_own_ciphersuite_preferences() const { return true; }
 bool Policy::negotiate_encrypt_then_mac() const { return true; }
 bool Policy::support_cert_status_message() const { return true; }
+bool Policy::allow_resumption_for_renegotiation() const { return true; }
+bool Policy::only_resume_with_exact_version() const { return true; }
+bool Policy::require_client_certificate_authentication() const { return false; }
+bool Policy::request_client_certificate_authentication() const { return require_client_certificate_authentication(); }
+bool Policy::abort_connection_on_undesired_renegotiation() const { return false; }
+
+size_t Policy::maximum_certificate_chain_size() const { return 0; }
 
 // 1 second initial timeout, 60 second max - see RFC 6347 sec 4.2.4.1
 size_t Policy::dtls_initial_timeout() const { return 1*1000; }
@@ -431,7 +440,11 @@ std::vector<uint16_t> Policy::ciphersuite_list(Protocol_Version version,
    for(auto&& suite : Ciphersuite::all_known_ciphersuites())
       {
       // Can we use it?
-      if(suite.valid() == false)
+      if(!suite.valid())
+         continue;
+
+      // Can we use it in this version?
+      if(!suite.usable_in_version(version))
          continue;
 
       // Is it acceptable to the policy?
@@ -442,17 +455,6 @@ std::vector<uint16_t> Policy::ciphersuite_list(Protocol_Version version,
       if(!have_srp && suite.kex_method() == Kex_Algo::SRP_SHA)
          continue;
 
-      if(!version.supports_aead_modes())
-         {
-         // Are we doing AEAD in a non-AEAD version?
-         if(suite.mac_algo() == "AEAD")
-            continue;
-
-         // Older (v1.0/v1.1) versions also do not support any hash but SHA-1
-         if(suite.mac_algo() != "SHA-1")
-            continue;
-         }
-
       if(!value_exists(kex, suite.kex_algo()))
          continue; // unsupported key exchange
 
diff --git a/src/lib/tls/tls_policy.h b/src/lib/tls/tls_policy.h
index 7b00b2e01..3a5be83d9 100644
--- a/src/lib/tls/tls_policy.h
+++ b/src/lib/tls/tls_policy.h
@@ -124,6 +124,14 @@ class BOTAN_PUBLIC_API(2,0) Policy
       */
       virtual bool allow_server_initiated_renegotiation() const;
 
+      /**
+      * If true, a request to renegotiate will close the connection with
+      * a fatal alert. Otherwise, a warning alert is sent.
+      */
+      virtual bool abort_connection_on_undesired_renegotiation() const;
+
+      virtual bool only_resume_with_exact_version() const;
+
       /**
       * Allow TLS v1.0
       */
@@ -270,6 +278,19 @@ class BOTAN_PUBLIC_API(2,0) Policy
       */
       virtual bool support_cert_status_message() const;
 
+      /**
+      * Indicate if client certificate authentication is required.
+      * If true, then a cert will be requested and if the client does
+      * not send a certificate the connection will be closed.
+      */
+      virtual bool require_client_certificate_authentication() const;
+
+      /**
+      * Indicate if client certificate authentication is requested.
+      * If true, then a cert will be requested.
+      */
+      virtual bool request_client_certificate_authentication() const;
+
       /**
       * Return allowed ciphersuites, in order of preference
       */
@@ -291,6 +312,14 @@ class BOTAN_PUBLIC_API(2,0) Policy
       */
       virtual size_t dtls_maximum_timeout() const;
 
+      /**
+      * @return the maximum size of the certificate chain, in bytes.
+      * Return 0 to disable this and accept any size.
+      */
+      virtual size_t maximum_certificate_chain_size() const;
+
+      virtual bool allow_resumption_for_renegotiation() const;
+
       /**
       * Convert this policy to a printable format.
       * @param o stream to be printed to
@@ -525,6 +554,8 @@ class BOTAN_PUBLIC_API(2,0) Text_Policy : public Policy
 
       bool support_cert_status_message() const override;
 
+      bool require_client_certificate_authentication() const override;
+
       size_t minimum_ecdh_group_size() const override;
 
       size_t minimum_ecdsa_group_size() const override;
diff --git a/src/lib/tls/tls_server.cpp b/src/lib/tls/tls_server.cpp
index 2b1885e45..d0c3baa2e 100644
--- a/src/lib/tls/tls_server.cpp
+++ b/src/lib/tls/tls_server.cpp
@@ -153,7 +153,7 @@ uint16_t choose_ciphersuite(
    const Policy& policy,
    Protocol_Version version,
    Credentials_Manager& creds,
-   const std::map<std::string, std::vector<X509_Certificate> >& cert_chains,
+   const std::map<std::string, std::vector<X509_Certificate>>& cert_chains,
    const Client_Hello& client_hello)
    {
    const bool our_choice = policy.server_uses_own_ciphersuite_preferences();
@@ -264,17 +264,17 @@ uint16_t choose_ciphersuite(
                        "Can't agree on a ciphersuite with client");
    }
 
-std::map<std::string, std::vector<X509_Certificate> >
+std::map<std::string, std::vector<X509_Certificate>>
 get_server_certs(const std::string& hostname,
                  Credentials_Manager& creds)
    {
-   const char* cert_types[] = { "RSA", "DSA", "ECDSA", nullptr };
+   const char* cert_types[] = { "RSA", "ECDSA", "DSA", nullptr };
 
-   std::map<std::string, std::vector<X509_Certificate> > cert_chains;
+   std::map<std::string, std::vector<X509_Certificate>> cert_chains;
 
    for(size_t i = 0; cert_types[i]; ++i)
       {
-      std::vector<X509_Certificate> certs =
+      const std::vector<X509_Certificate> certs =
          creds.cert_chain_single_type(cert_types[i], "tls-server", hostname);
 
       if(!certs.empty())
@@ -321,7 +321,6 @@ Server::Server(output_fn output,
    {
    }
 
-
 Server::Server(output_fn output,
                data_cb got_data_cb,
                alert_cb recv_alert_cb,
@@ -372,53 +371,29 @@ void Server::initiate_handshake(Handshake_State& state,
    Hello_Request hello_req(state.handshake_io());
    }
 
-/*
-* Process a CLIENT HELLO Message
-*/
-void Server::process_client_hello_msg(const Handshake_State* active_state,
-                                      Server_Handshake_State& pending_state,
-                                      const std::vector<uint8_t>& contents)
-   {
-   const bool initial_handshake = !active_state;
+namespace {
 
-   if(initial_handshake == false && policy().allow_client_initiated_renegotiation() == false)
-      {
-      send_warning_alert(Alert::NO_RENEGOTIATION);
-      return;
-      }
+Protocol_Version select_version(const Botan::TLS::Policy& policy,
+                                Protocol_Version client_offer,
+                                Protocol_Version active_version,
+                                bool is_fallback)
+   {
+   const Protocol_Version latest_supported =
+      policy.latest_supported_version(client_offer.is_datagram_protocol());
 
-   if(!policy().allow_insecure_renegotiation() &&
-      !(initial_handshake || secure_renegotiation_supported()))
+   if(is_fallback)
       {
-      send_warning_alert(Alert::NO_RENEGOTIATION);
-      return;
+      if(latest_supported > client_offer)
+         throw TLS_Exception(Alert::INAPPROPRIATE_FALLBACK,
+                              "Client signalled fallback SCSV, possible attack");
       }
 
-   pending_state.client_hello(new Client_Hello(contents));
-   const Protocol_Version client_version = pending_state.client_hello()->version();
-
-   if(client_version.major_version() < 3)
-      throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client offered version with major version under 3");
-   if(client_version.major_version() == 3 && client_version.minor_version() == 0)
-      throw TLS_Exception(Alert::PROTOCOL_VERSION, "SSLv3 is not supported");
+   const bool initial_handshake = (active_version.valid() == false);
 
-   Protocol_Version negotiated_version;
-
-   const Protocol_Version latest_supported =
-      policy().latest_supported_version(client_version.is_datagram_protocol());
+   const bool client_offer_acceptable =
+      client_offer.known_version() && policy.acceptable_protocol_version(client_offer);
 
-   if((initial_handshake && client_version.known_version()) ||
-      (!initial_handshake && client_version == active_state->version()))
-      {
-      /*
-      Common cases: new client hello with some known version, or a
-      renegotiation using the same version as previously
-      negotiated.
-      */
-
-      negotiated_version = client_version;
-      }
-   else if(!initial_handshake && (client_version != active_state->version()))
+   if(!initial_handshake)
       {
       /*
       * If this is a renegotiation, and the client has offered a
@@ -427,40 +402,80 @@ void Server::process_client_hello_msg(const Handshake_State* active_state,
       * client is offering a version earlier than what it initially
       * negotiated, reject as a probable attack.
       */
-      if(active_state->version() > client_version)
+      if(active_version > client_offer)
          {
          throw TLS_Exception(Alert::PROTOCOL_VERSION,
                               "Client negotiated " +
-                              active_state->version().to_string() +
+                              active_version.to_string() +
                               " then renegotiated with " +
-                              client_version.to_string());
+                              client_offer.to_string());
          }
       else
-         negotiated_version = active_state->version();
+         {
+         return active_version;
+         }
       }
-   else
+   else if(client_offer_acceptable)
+      {
+      return client_offer;
+      }
+   else if(!client_offer.known_version() || client_offer > latest_supported)
       {
       /*
-      New negotiation using a version we don't know. Offer them the
-      best we currently know and support
+      The client offered some version newer than the latest we
+      support.  Offer them the best we know.
       */
-      negotiated_version = latest_supported;
+      return latest_supported;
       }
-
-   if(!policy().acceptable_protocol_version(negotiated_version))
+   else
       {
       throw TLS_Exception(Alert::PROTOCOL_VERSION,
-                           "Client version " + negotiated_version.to_string() +
+                           "Client version " + client_offer.to_string() +
                            " is unacceptable by policy");
       }
+   }
+
+}
 
-   if(pending_state.client_hello()->sent_fallback_scsv())
+/*
+* Process a CLIENT HELLO Message
+*/
+void Server::process_client_hello_msg(const Handshake_State* active_state,
+                                      Server_Handshake_State& pending_state,
+                                      const std::vector<uint8_t>& contents)
+   {
+   const bool initial_handshake = !active_state;
+
+   if(initial_handshake == false && policy().allow_client_initiated_renegotiation() == false)
       {
-      if(latest_supported > client_version)
-         throw TLS_Exception(Alert::INAPPROPRIATE_FALLBACK,
-                              "Client signalled fallback SCSV, possible attack");
+      send_warning_alert(Alert::NO_RENEGOTIATION);
+      return;
       }
 
+   if(!policy().allow_insecure_renegotiation() &&
+      !(initial_handshake || secure_renegotiation_supported()))
+      {
+      send_warning_alert(Alert::NO_RENEGOTIATION);
+      return;
+      }
+
+   pending_state.client_hello(new Client_Hello(contents));
+   const Protocol_Version client_offer = pending_state.client_hello()->version();
+
+   if(client_offer.major_version() < 3)
+      throw TLS_Exception(Alert::PROTOCOL_VERSION, "Client offered version with major version under 3");
+   if(client_offer.major_version() == 3 && client_offer.minor_version() == 0)
+      throw TLS_Exception(Alert::PROTOCOL_VERSION, "SSLv3 is not supported");
+
+   const Protocol_Version negotiated_version =
+      select_version(policy(), client_offer,
+                     active_state ? active_state->version() : Protocol_Version(),
+                     pending_state.client_hello()->sent_fallback_scsv());
+
+   const auto compression_methods = pending_state.client_hello()->compression_methods();
+   if(!value_exists(compression_methods, uint8_t(0)))
+      throw TLS_Exception(Alert::ILLEGAL_PARAMETER, "Client did not offer NULL compression");
+
    secure_renegotiation_check(pending_state.client_hello());
 
    pending_state.set_version(negotiated_version);
@@ -511,6 +526,11 @@ void Server::process_certificate_msg(Server_Handshake_State& pending_state,
                                      const std::vector<uint8_t>& contents)
    {
    pending_state.client_certs(new Certificate(contents, policy()));
+
+   // CERTIFICATE_REQUIRED would make more sense but BoGo expects handshake failure alert
+   if(pending_state.client_certs()->empty() && policy().require_client_certificate_authentication())
+      throw TLS_Exception(Alert::HANDSHAKE_FAILURE, "Policy requires client send a certificate, but it did not");
+
    pending_state.set_expected_next(CLIENT_KEX);
    }
 
@@ -767,7 +787,7 @@ void Server::session_resume(Server_Handshake_State& pending_state,
 void Server::session_create(Server_Handshake_State& pending_state,
                             bool have_session_ticket_key)
    {
-   std::map<std::string, std::vector<X509_Certificate> > cert_chains;
+   std::map<std::string, std::vector<X509_Certificate>> cert_chains;
 
    const std::string sni_hostname = pending_state.client_hello()->sni_hostname();
 
@@ -782,10 +802,10 @@ void Server::session_create(Server_Handshake_State& pending_state,
       * find any certs for the requested name but did find at
       * least one cert to use in general. That avoids sending an
       * unrecognized_name when a server is configured for purely
-      * anonymous operation.
+      * anonymous/PSK operation.
       */
       if(!cert_chains.empty())
-         send_alert(Alert(Alert::UNRECOGNIZED_NAME));
+         send_warning_alert(Alert::UNRECOGNIZED_NAME);
       }
 
    const uint16_t ciphersuite = choose_ciphersuite(policy(), pending_state.version(),
@@ -857,7 +877,11 @@ void Server::session_create(Server_Handshake_State& pending_state,
       client_auth_CAs.insert(client_auth_CAs.end(), subjects.begin(), subjects.end());
       }
 
-   if(!client_auth_CAs.empty() && pending_state.ciphersuite().signature_used())
+   const bool request_cert =
+      (client_auth_CAs.empty() == false) ||
+      policy().request_client_certificate_authentication();
+
+   if(request_cert && pending_state.ciphersuite().signature_used())
       {
       pending_state.cert_req(
          new Certificate_Req(pending_state.handshake_io(),
diff --git a/src/lib/tls/tls_server.h b/src/lib/tls/tls_server.h
index c55c3f93d..e6536934a 100644
--- a/src/lib/tls/tls_server.h
+++ b/src/lib/tls/tls_server.h
@@ -110,7 +110,7 @@ class BOTAN_PUBLIC_API(2,0) Server final : public Channel
       * tied to the session and a later renegotiation of the same
       * session can choose a new protocol.
       */
-      std::string application_protocol() const { return m_next_protocol; }
+      std::string application_protocol() const override { return m_next_protocol; }
 
    private:
       std::vector<X509_Certificate>
diff --git a/src/lib/tls/tls_text_policy.cpp b/src/lib/tls/tls_text_policy.cpp
index 829899fbc..1b83f0dbd 100644
--- a/src/lib/tls/tls_text_policy.cpp
+++ b/src/lib/tls/tls_text_policy.cpp
@@ -80,6 +80,11 @@ bool Text_Policy::include_time_in_hello_random() const
    return get_bool("include_time_in_hello_random", Policy::include_time_in_hello_random());
    }
 
+bool Text_Policy::require_client_certificate_authentication() const
+   {
+   return get_bool("require_client_certificate_authentication", Policy::require_client_certificate_authentication());
+   }
+
 bool Text_Policy::allow_client_initiated_renegotiation() const
    {
    return get_bool("allow_client_initiated_renegotiation", Policy::allow_client_initiated_renegotiation());
diff --git a/src/lib/tls/tls_version.h b/src/lib/tls/tls_version.h
index 569030085..13be64316 100644
--- a/src/lib/tls/tls_version.h
+++ b/src/lib/tls/tls_version.h
@@ -81,6 +81,11 @@ class BOTAN_PUBLIC_API(2,0) Protocol_Version final
       */
       uint8_t minor_version() const { return get_byte(1, m_version); }
 
+      /**
+      * @return the version code
+      */
+      uint16_t version_code() const { return m_version; }
+
       /**
       * @return human-readable description of this version
       */


Test patch:
diff --git a/src/tests/data/tls/alert.vec b/src/tests/data/tls/alert.vec
index cd5058212..e58b2cd1e 100644
--- a/src/tests/data/tls/alert.vec
+++ b/src/tests/data/tls/alert.vec
@@ -14,11 +14,11 @@ Exception =
 
 Buffer = 0030
 Protocol = 0303
-Exception = Alert: Bad code for alert level
+Exception = Bad code for TLS alert level
 
 Buffer = 02
-Exception = Alert: Bad size 1 for alert message
+Exception = Bad size (1) for TLS alert message
 
 Buffer = 020101
-Exception = Alert: Bad size 3 for alert message
+Exception = Bad size (3) for TLS alert message
 
diff --git a/src/tests/test_tls_messages.cpp b/src/tests/test_tls_messages.cpp
index 364fdc0bf..a79f5b42e 100644
--- a/src/tests/test_tls_messages.cpp
+++ b/src/tests/test_tls_messages.cpp
@@ -123,17 +123,15 @@ class TLS_Message_Parsing_Test final : public Text_Based_Test
                else if(algo == "cert_status")
                   {
                   Botan::TLS::Certificate_Status message(buffer);
-                  std::shared_ptr<const Botan::OCSP::Response> resp = message.response();
 
-                  if(result.confirm("Decoded response", resp != nullptr))
-                     {
-                     const std::vector<std::string> CNs = resp->signer_name().get_attribute("CN");
+                  Botan::OCSP::Response resp(message.response());
+
+                  const std::vector<std::string> CNs = resp.signer_name().get_attribute("CN");
 
-                     // This is not requird by OCSP protocol, we are just using it as a test here
-                     if(result.test_eq("OCSP response has signer name", CNs.size(), 1))
-                        {
-                        result.test_eq("Expected name", CNs[0], expected_name);
-                        }
+                  // This is not requird by OCSP protocol, we are just using it as a test here
+                  if(result.test_eq("OCSP response has signer name", CNs.size(), 1))
+                     {
+                     result.test_eq("Expected name", CNs[0], expected_name);
                      }
                   }
                else