]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ssl/bio.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / ssl / bio.cc
index 55e49fc07cf0644f793a49f4b4c0cd5136d3df89..cc36f4306f091fcea509d6f69e5d54bbfe5bfab2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
 #if USE_OPENSSL
 
 #include "comm.h"
+#include "fd.h"
 #include "fde.h"
 #include "globals.h"
 #include "ip/Address.h"
+#include "parser/BinaryTokenizer.h"
+#include "SquidTime.h"
 #include "ssl/bio.h"
 
-#if HAVE_OPENSSL_SSL_H
-#include <openssl/ssl.h>
-#endif
-
 #if _SQUID_WINDOWS_
 extern int socket_read_method(int, char *, int);
 extern int socket_write_method(int, const char *, int);
@@ -40,6 +39,9 @@ static int squid_bio_destroy(BIO *data);
 /* SSL callbacks */
 static void squid_ssl_info(const SSL *ssl, int where, int ret);
 
+#if HAVE_LIBCRYPTO_BIO_METH_NEW
+static BIO_METHOD *SquidMethods = nullptr;
+#else
 /// Initialization structure for the BIO table with
 /// Squid-specific methods and BIO method wrappers.
 static BIO_METHOD SquidMethods = {
@@ -54,11 +56,28 @@ static BIO_METHOD SquidMethods = {
     squid_bio_destroy,
     NULL // squid_callback_ctrl not supported
 };
+#endif
 
 BIO *
-Ssl::Bio::Create(const int fd, Ssl::Bio::Type type)
-{
-    if (BIO *bio = BIO_new(&SquidMethods)) {
+Ssl::Bio::Create(const int fd, Security::Io::Type type)
+{
+#if HAVE_LIBCRYPTO_BIO_METH_NEW
+    if (!SquidMethods) {
+        SquidMethods = BIO_meth_new(BIO_TYPE_SOCKET, "squid");
+        BIO_meth_set_write(SquidMethods, squid_bio_write);
+        BIO_meth_set_read(SquidMethods, squid_bio_read);
+        BIO_meth_set_puts(SquidMethods, squid_bio_puts);
+        BIO_meth_set_gets(SquidMethods, NULL);
+        BIO_meth_set_ctrl(SquidMethods, squid_bio_ctrl);
+        BIO_meth_set_create(SquidMethods, squid_bio_create);
+        BIO_meth_set_destroy(SquidMethods, squid_bio_destroy);
+    }
+    BIO_METHOD *useMethod = SquidMethods;
+#else
+    BIO_METHOD *useMethod = &SquidMethods;
+#endif
+
+    if (BIO *bio = BIO_new(useMethod)) {
         BIO_int_ctrl(bio, BIO_C_SET_FD, type, fd);
         return bio;
     }
@@ -127,28 +146,6 @@ Ssl::Bio::read(char *buf, int size, BIO *table)
     return result;
 }
 
-int
-Ssl::Bio::readAndBuffer(char *buf, int size, BIO *table, const char *description)
-{
-    prepReadBuf();
-
-    size = min((int)rbuf.potentialSpaceSize(), size);
-    if (size <= 0) {
-        debugs(83, DBG_IMPORTANT, "Not enough space to hold " <<
-               rbuf.contentSize() << "+ byte " << description);
-        return -1;
-    }
-
-    const int bytes = Ssl::Bio::read(buf, size, table);
-    debugs(83, 5, "read " << bytes << " out of " << size << " bytes"); // move to Ssl::Bio::read()
-
-    if (bytes > 0) {
-        rbuf.append(buf, bytes);
-        debugs(83, 5, "recorded " << bytes << " bytes of " << description);
-    }
-    return bytes;
-}
-
 /// Called whenever the SSL connection state changes, an alert appears, or an
 /// error occurs. See SSL_set_info_callback().
 void
@@ -167,34 +164,46 @@ Ssl::Bio::stateChanged(const SSL *ssl, int where, int ret)
            SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")");
 }
 
-void
-Ssl::Bio::prepReadBuf()
+Ssl::ClientBio::ClientBio(const int anFd):
+    Bio(anFd),
+    holdRead_(false),
+    holdWrite_(false),
+    helloSize(0),
+    abortReason(nullptr)
 {
-    if (rbuf.isNull())
-        rbuf.init(4096, 65536);
-}
-
-bool
-Ssl::ClientBio::isClientHello(int state)
-{
-    return (
-               state == SSL3_ST_SR_CLNT_HELLO_A ||
-               state == SSL23_ST_SR_CLNT_HELLO_A ||
-               state == SSL23_ST_SR_CLNT_HELLO_B ||
-               state == SSL3_ST_SR_CLNT_HELLO_B ||
-               state == SSL3_ST_SR_CLNT_HELLO_C
-           );
+    renegotiations.configure(10*1000);
 }
 
 void
 Ssl::ClientBio::stateChanged(const SSL *ssl, int where, int ret)
 {
     Ssl::Bio::stateChanged(ssl, where, ret);
+    // detect client-initiated renegotiations DoS (CVE-2011-1473)
+    if (where & SSL_CB_HANDSHAKE_START) {
+        const int reneg = renegotiations.count(1);
+
+        if (abortReason)
+            return; // already decided and informed the admin
+
+        if (reneg > RenegotiationsLimit) {
+            abortReason = "renegotiate requests flood";
+            debugs(83, DBG_IMPORTANT, "Terminating TLS connection [from " << fd_table[fd_].ipaddr << "] due to " << abortReason << ". This connection received " <<
+                   reneg << " renegotiate requests in the last " <<
+                   RenegotiationsWindow << " seconds (and " <<
+                   renegotiations.remembered() << " requests total).");
+        }
+    }
 }
 
 int
 Ssl::ClientBio::write(const char *buf, int size, BIO *table)
 {
+    if (abortReason) {
+        debugs(83, 3, "BIO on FD " << fd_ << " is aborted");
+        BIO_clear_retry_flags(table);
+        return -1;
+    }
+
     if (holdWrite_) {
         BIO_set_retry_write(table);
         return 0;
@@ -203,52 +212,13 @@ Ssl::ClientBio::write(const char *buf, int size, BIO *table)
     return Ssl::Bio::write(buf, size, table);
 }
 
-const char *objToString(unsigned char const *bytes, int len)
-{
-    static std::string buf;
-    buf.clear();
-    for (int i = 0; i < len; i++ ) {
-        char tmp[3];
-        snprintf(tmp, sizeof(tmp), "%.2x", bytes[i]);
-        buf.append(tmp);
-    }
-    return buf.c_str();
-}
-
 int
 Ssl::ClientBio::read(char *buf, int size, BIO *table)
 {
-    if (helloState < atHelloReceived) {
-        int bytes = readAndBuffer(buf, size, table, "TLS client Hello");
-        if (bytes <= 0)
-            return bytes;
-    }
-
-    if (helloState == atHelloNone) {
-        helloSize = receivedHelloFeatures_.parseMsgHead(rbuf);
-        if (helloSize == 0) {
-            // Not enough bytes to get hello message size
-            BIO_set_retry_read(table);
-            return -1;
-        } else if (helloSize < 0) {
-            wrongProtocol = true;
-            return -1;
-        }
-
-        helloState = atHelloStarted; //Next state
-    }
-
-    if (helloState == atHelloStarted) {
-        const unsigned char *head = (const unsigned char *)rbuf.content();
-        const char *s = objToString(head, rbuf.contentSize());
-        debugs(83, 7, "SSL Header: " << s);
-
-        if (helloSize > rbuf.contentSize()) {
-            BIO_set_retry_read(table);
-            return -1;
-        }
-        receivedHelloFeatures_.get(rbuf);
-        helloState = atHelloReceived;
+    if (abortReason) {
+        debugs(83, 3, "BIO on FD " << fd_ << " is aborted");
+        BIO_clear_retry_flags(table);
+        return -1;
     }
 
     if (holdRead_) {
@@ -257,19 +227,33 @@ Ssl::ClientBio::read(char *buf, int size, BIO *table)
         return -1;
     }
 
-    if (helloState == atHelloReceived) {
-        if (rbuf.hasContent()) {
-            int bytes = (size <= rbuf.contentSize() ? size : rbuf.contentSize());
-            memcpy(buf, rbuf.content(), bytes);
-            rbuf.consume(bytes);
-            return bytes;
-        } else
-            return Ssl::Bio::read(buf, size, table);
-    }
+    if (!rbuf.isEmpty()) {
+        int bytes = (size <= (int)rbuf.length() ? size : rbuf.length());
+        memcpy(buf, rbuf.rawContent(), bytes);
+        rbuf.consume(bytes);
+        return bytes;
+    } else
+        return Ssl::Bio::read(buf, size, table);
 
     return -1;
 }
 
+Ssl::ServerBio::ServerBio(const int anFd):
+    Bio(anFd),
+    helloMsgSize(0),
+    helloBuild(false),
+    allowSplice(false),
+    allowBump(false),
+    holdWrite_(false),
+    record_(false),
+    parsedHandshake(false),
+    parseError(false),
+    bumpMode_(bumpNone),
+    rbufConsumePos(0),
+    parser_(Security::HandshakeParser::fromServer)
+{
+}
+
 void
 Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret)
 {
@@ -277,16 +261,94 @@ Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret)
 }
 
 void
-Ssl::ServerBio::setClientFeatures(const Ssl::Bio::sslFeatures &features)
+Ssl::ServerBio::setClientFeatures(Security::TlsDetails::Pointer const &details, SBuf const &aHello)
 {
-    clientFeatures = features;
+    clientTlsDetails = details;
+    clientSentHello = aHello;
 };
 
 int
 Ssl::ServerBio::read(char *buf, int size, BIO *table)
 {
-    return record_ ?
-           readAndBuffer(buf, size, table, "TLS server Hello") : Ssl::Bio::read(buf, size, table);
+    if (parsedHandshake) // done parsing TLS Hello
+        return readAndGive(buf, size, table);
+    else
+        return readAndParse(buf, size, table);
+}
+
+/// Read and give everything to OpenSSL.
+int
+Ssl::ServerBio::readAndGive(char *buf, const int size, BIO *table)
+{
+    // If we have unused buffered bytes, give those bytes to OpenSSL now,
+    // before reading more. TODO: Read if we have buffered less than size?
+    if (rbufConsumePos < rbuf.length())
+        return giveBuffered(buf, size);
+
+    if (record_) {
+        const int result = readAndBuffer(table);
+        if (result <= 0)
+            return result;
+        return giveBuffered(buf, size);
+    }
+
+    return Ssl::Bio::read(buf, size, table);
+}
+
+/// Read and give everything to our parser.
+/// When/if parsing is finished (successfully or not), start giving to OpenSSL.
+int
+Ssl::ServerBio::readAndParse(char *buf, const int size, BIO *table)
+{
+    const int result = readAndBuffer(table);
+    if (result <= 0)
+        return result;
+
+    try {
+        if (!parser_.parseHello(rbuf)) {
+            // need more data to finish parsing
+            BIO_set_retry_read(table);
+            return -1;
+        }
+        parsedHandshake = true; // done parsing (successfully)
+    }
+    catch (const std::exception &ex) {
+        debugs(83, 2, "parsing error on FD " << fd_ << ": " << ex.what());
+        parsedHandshake = true; // done parsing (due to an error)
+        parseError = true;
+    }
+
+    return giveBuffered(buf, size);
+}
+
+/// Reads more data into the read buffer. Returns either the number of bytes
+/// read or, on errors (including "try again" errors), a negative number.
+int
+Ssl::ServerBio::readAndBuffer(BIO *table)
+{
+    char *space = rbuf.rawAppendStart(SQUID_TCP_SO_RCVBUF);
+    const int result = Ssl::Bio::read(space, SQUID_TCP_SO_RCVBUF, table);
+    if (result <= 0)
+        return result;
+
+    rbuf.rawAppendFinish(space, result);
+    return result;
+}
+
+/// give previously buffered bytes to OpenSSL
+/// returns the number of bytes given
+int
+Ssl::ServerBio::giveBuffered(char *buf, const int size)
+{
+    if (rbuf.length() <= rbufConsumePos)
+        return -1; // buffered nothing yet
+
+    const int unsent = rbuf.length() - rbufConsumePos;
+    const int bytes = (size <= unsent ? size : unsent);
+    memcpy(buf, rbuf.rawContent() + rbufConsumePos, bytes);
+    rbufConsumePos += bytes;
+    debugs(83, 7, bytes << "<=" << size << " bytes to OpenSSL");
+    return bytes;
 }
 
 // This function makes the required checks to examine if the client hello
@@ -297,9 +359,12 @@ Ssl::ServerBio::read(char *buf, int size, BIO *table)
 // This is mostly possible in the cases where the web client uses openSSL
 // library similar with this one used by squid.
 static bool
-adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features)
+adjustSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, SBuf &helloMessage)
 {
 #if SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK
+    if (!details)
+        return false;
+
     if (!ssl->s3) {
         debugs(83, 5, "No SSLv3 data found!");
         return false;
@@ -308,107 +373,78 @@ adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features)
     // If the client supports compression but our context does not support
     // we can not adjust.
 #if !defined(OPENSSL_NO_COMP)
-    const bool requireCompression = (features.compressMethod && ssl->ctx->comp_methods == NULL);
+    const bool requireCompression = (details->compressionSupported && ssl->ctx->comp_methods == nullptr);
 #else
-    const bool requireCompression = features.compressMethod;
+    const bool requireCompression = details->compressionSupported;
 #endif
     if (requireCompression) {
         debugs(83, 5, "Client Hello Data supports compression, but we do not!");
         return false;
     }
 
-    // Check ciphers list
-    size_t token = 0;
-    size_t end = 0;
-    while (token != std::string::npos) {
-        end = features.clientRequestedCiphers.find(':',token);
-        std::string cipher;
-        cipher.assign(features.clientRequestedCiphers, token, end - token);
-        token = (end != std::string::npos ? end + 1 : std::string::npos);
-        bool found = false;
-        STACK_OF(SSL_CIPHER) *cipher_stack = SSL_get_ciphers(ssl);
-        for (int i = 0; i < sk_SSL_CIPHER_num(cipher_stack); i++) {
-            SSL_CIPHER *c = sk_SSL_CIPHER_value(cipher_stack, i);
-            const char *cname = SSL_CIPHER_get_name(c);
-            if (cipher.compare(cname)) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            debugs(83, 5, "Client Hello Data supports cipher '"<< cipher <<"' but we do not support it!");
-            return false;
-        }
-    }
-
 #if !defined(SSL_TLSEXT_HB_ENABLED)
-    if (features.doHeartBeats) {
+    if (details->doHeartBeats) {
         debugs(83, 5, "Client Hello Data supports HeartBeats but we do not support!");
         return false;
     }
 #endif
 
-    for (std::list<int>::iterator it = features.extensions.begin(); it != features.extensions.end(); ++it) {
-        static int supportedExtensions[] = {
-#if defined(TLSEXT_TYPE_server_name)
-            TLSEXT_TYPE_server_name,
-#endif
-#if defined(TLSEXT_TYPE_opaque_prf_input)
-            TLSEXT_TYPE_opaque_prf_input,
-#endif
-#if defined(TLSEXT_TYPE_heartbeat)
-            TLSEXT_TYPE_heartbeat,
-#endif
+    if (details->unsupportedExtensions) {
+        debugs(83, 5, "Client Hello contains extensions that we do not support!");
+        return false;
+    }
+
+    SSL3_BUFFER *wb=&(ssl->s3->wbuf);
+    if (wb->len < (size_t)helloMessage.length()) {
+        debugs(83, 5, "Client Hello exceeds OpenSSL buffer: " << helloMessage.length() << " >= " << wb->len);
+        return false;
+    }
+
+    /* Check whether all on-the-wire ciphers are supported by OpenSSL. */
+
+    const auto &wireCiphers = details->ciphers;
+    Security::TlsDetails::Ciphers::size_type ciphersToFind = wireCiphers.size();
+
+    // RFC 5746: "TLS_EMPTY_RENEGOTIATION_INFO_SCSV is not a true cipher suite".
+    // It is commonly seen on the wire, including in from-OpenSSL traffic, but
+    // SSL_get_ciphers() does not return this _pseudo_ cipher suite in my tests.
+    // If OpenSSL supports scsvCipher, we count it (at most once) further below.
 #if defined(TLSEXT_TYPE_renegotiate)
-            TLSEXT_TYPE_renegotiate,
-#endif
-#if defined(TLSEXT_TYPE_ec_point_formats)
-            TLSEXT_TYPE_ec_point_formats,
-#endif
-#if defined(TLSEXT_TYPE_elliptic_curves)
-            TLSEXT_TYPE_elliptic_curves,
-#endif
-#if defined(TLSEXT_TYPE_session_ticket)
-            TLSEXT_TYPE_session_ticket,
-#endif
-#if defined(TLSEXT_TYPE_status_request)
-            TLSEXT_TYPE_status_request,
-#endif
-#if defined(TLSEXT_TYPE_use_srtp)
-            TLSEXT_TYPE_use_srtp,
-#endif
-#if 0 //Allow 13172 Firefox supported extension for testing purposes
-            13172,
+    // the 0x00FFFF mask converts 3-byte OpenSSL cipher to our 2-byte cipher
+    const uint16_t scsvCipher = SSL3_CK_SCSV & 0x00FFFF;
+#else
+    const uint16_t scsvCipher = 0;
 #endif
-            -1
-        };
-        bool found = false;
-        for (int i = 0; supportedExtensions[i] != -1; i++) {
-            if (*it == supportedExtensions[i]) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            debugs(83, 5, "Extension " << *it <<  " does not supported!");
-            return false;
-        }
+
+    STACK_OF(SSL_CIPHER) *cipher_stack = SSL_get_ciphers(ssl);
+    const int supportedCipherCount = sk_SSL_CIPHER_num(cipher_stack);
+    for (int idx = 0; idx < supportedCipherCount && ciphersToFind > 0; ++idx) {
+        const SSL_CIPHER *cipher = sk_SSL_CIPHER_value(cipher_stack, idx);
+        const auto id = SSL_CIPHER_get_id(cipher) & 0x00FFFF;
+        if (wireCiphers.find(id) != wireCiphers.end() && (!scsvCipher || id != scsvCipher))
+            --ciphersToFind;
     }
 
-    SSL3_BUFFER *wb=&(ssl->s3->wbuf);
-    if (wb->len < (size_t)features.helloMessage.length())
+    if (ciphersToFind > 0 && scsvCipher && wireCiphers.find(scsvCipher) != wireCiphers.end())
+        --ciphersToFind;
+
+    if (ciphersToFind > 0) {
+        // TODO: Add slowlyReportUnsupportedCiphers() to slowly find and report each of them
+        debugs(83, 5, "Client Hello Data has " << ciphersToFind << " ciphers that we do not support!");
         return false;
+    }
 
     debugs(83, 5, "OpenSSL SSL struct will be adjusted to mimic client hello data!");
 
     //Adjust ssl structure data.
     // We need to fix the random in SSL struct:
-    memcpy(ssl->s3->client_random, features.client_random, SSL3_RANDOM_SIZE);
-    memcpy(wb->buf, features.helloMessage.rawContent(), features.helloMessage.length());
-    wb->left = features.helloMessage.length();
+    if (details->clientRandom.length() == SSL3_RANDOM_SIZE)
+        memcpy(ssl->s3->client_random, details->clientRandom.c_str(), SSL3_RANDOM_SIZE);
+    memcpy(wb->buf, helloMessage.rawContent(), helloMessage.length());
+    wb->left = helloMessage.length();
 
-    size_t mainHelloSize = features.helloMessage.length() - 5;
-    const char *mainHello = features.helloMessage.rawContent() + 5;
+    size_t mainHelloSize = helloMessage.length() - 5;
+    const char *mainHello = helloMessage.rawContent() + 5;
     assert((size_t)ssl->init_buf->max > mainHelloSize);
     memcpy(ssl->init_buf->data, mainHello, mainHelloSize);
     debugs(83, 5, "Hello Data init and adjustd sizes :" << ssl->init_num << " = "<< mainHelloSize);
@@ -426,39 +462,43 @@ Ssl::ServerBio::write(const char *buf, int size, BIO *table)
 {
 
     if (holdWrite_) {
-        debugs(83, 7,  "Hold write, for SSL connection on " << fd_ << "will not write bytes of size " << size);
+        debugs(83, 7, "postpone writing " << size << " bytes to SSL FD " << fd_);
         BIO_set_retry_write(table);
         return -1;
     }
 
     if (!helloBuild && (bumpMode_ == Ssl::bumpPeek || bumpMode_ == Ssl::bumpStare)) {
-        if (
-            buf[1] >= 3  //it is an SSL Version3 message
-            && buf[0] == 0x16 // and it is a Handshake/Hello message
-        ) {
-
-            //Hello message is the first message we write to server
-            assert(helloMsg.isEmpty());
-
-            auto ssl = fd_table[fd_].ssl.get();
-            if (clientFeatures.initialized_ && ssl) {
-                if (bumpMode_ == Ssl::bumpPeek) {
-                    if (adjustSSL(ssl, clientFeatures))
-                        allowBump = true;
-                    allowSplice = true;
-                    helloMsg.append(clientFeatures.helloMessage);
-                    debugs(83, 7,  "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for peek mode");
-                } else { /*Ssl::bumpStare*/
+        // buf contains OpenSSL-generated ClientHello. We assume it has a
+        // complete ClientHello and nothing else, but cannot fully verify
+        // that quickly. We only verify that buf starts with a v3+ record
+        // containing ClientHello.
+        Must(size >= 2); // enough for version and content_type checks below
+        Must(buf[1] >= 3); // record's version.major; determines buf[0] meaning
+        Must(buf[0] == 22); // TLSPlaintext.content_type == handshake in v3+
+
+        //Hello message is the first message we write to server
+        assert(helloMsg.isEmpty());
+
+        if (auto ssl = fd_table[fd_].ssl.get()) {
+            if (bumpMode_ == Ssl::bumpPeek) {
+                // we should not be here if we failed to parse the client-sent ClientHello
+                Must(!clientSentHello.isEmpty());
+                if (adjustSSL(ssl, clientTlsDetails, clientSentHello))
                     allowBump = true;
-                    if (adjustSSL(ssl, clientFeatures)) {
-                        allowSplice = true;
-                        helloMsg.append(clientFeatures.helloMessage);
-                        debugs(83, 7,  "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for stare mode");
-                    }
+                allowSplice = true;
+                // Replace OpenSSL-generated ClientHello with client-sent one.
+                helloMsg.append(clientSentHello);
+                debugs(83, 7,  "FD " << fd_ << ": Using client-sent ClientHello for peek mode");
+            } else { /*Ssl::bumpStare*/
+                allowBump = true;
+                if (!clientSentHello.isEmpty() && adjustSSL(ssl, clientTlsDetails, clientSentHello)) {
+                    allowSplice = true;
+                    helloMsg.append(clientSentHello);
+                    debugs(83, 7,  "FD " << fd_ << ": Using client-sent ClientHello for stare mode");
                 }
             }
         }
-        // If we do not build any hello message, copy the current
+        // if we did not use the client-sent ClientHello, then use the OpenSSL-generated one
         if (helloMsg.isEmpty())
             helloMsg.append(buf, size);
 
@@ -502,38 +542,32 @@ Ssl::ServerBio::flush(BIO *table)
     }
 }
 
-void
-Ssl::ServerBio::extractHelloFeatures()
+bool
+Ssl::ServerBio::resumingSession()
 {
-    if (!receivedHelloFeatures_.initialized_)
-        receivedHelloFeatures_.get(rbuf, false);
+    return parser_.resumingSession;
 }
 
 bool
-Ssl::ServerBio::resumingSession()
+Ssl::ServerBio::encryptedCertificates() const
 {
-    extractHelloFeatures();
-
-    if (!clientFeatures.sessionId.isEmpty() && !receivedHelloFeatures_.sessionId.isEmpty())
-        return clientFeatures.sessionId == receivedHelloFeatures_.sessionId;
-
-    // is this a session resuming attempt using TLS tickets?
-    if (clientFeatures.hasTlsTicket &&
-            receivedHelloFeatures_.tlsTicketsExtension &&
-            receivedHelloFeatures_.hasCcsOrNst)
-        return true;
-
-    return false;
+    return parser_.details->tlsSupportedVersion &&
+           Security::Tls1p3orLater(parser_.details->tlsSupportedVersion);
 }
 
 /// initializes BIO table after allocation
 static int
 squid_bio_create(BIO *bi)
 {
+#if !HAVE_LIBCRYPTO_BIO_GET_INIT
     bi->init = 0; // set when we store Bio object and socket fd (BIO_C_SET_FD)
     bi->num = 0;
-    bi->ptr = NULL;
     bi->flags = 0;
+#else
+    // No need to set more, openSSL initialize BIO memory to zero.
+#endif
+
+    BIO_set_data(bi, NULL);
     return 1;
 }
 
@@ -541,8 +575,8 @@ squid_bio_create(BIO *bi)
 static int
 squid_bio_destroy(BIO *table)
 {
-    delete static_cast<Ssl::Bio*>(table->ptr);
-    table->ptr = NULL;
+    delete static_cast<Ssl::Bio*>(BIO_get_data(table));
+    BIO_set_data(table, NULL);
     return 1;
 }
 
@@ -550,7 +584,7 @@ squid_bio_destroy(BIO *table)
 static int
 squid_bio_write(BIO *table, const char *buf, int size)
 {
-    Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
+    Ssl::Bio *bio = static_cast<Ssl::Bio*>(BIO_get_data(table));
     assert(bio);
     return bio->write(buf, size, table);
 }
@@ -559,7 +593,7 @@ squid_bio_write(BIO *table, const char *buf, int size)
 static int
 squid_bio_read(BIO *table, char *buf, int size)
 {
-    Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
+    Ssl::Bio *bio = static_cast<Ssl::Bio*>(BIO_get_data(table));
     assert(bio);
     return bio->read(buf, size, table);
 }
@@ -583,19 +617,19 @@ squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2)
         assert(arg2);
         const int fd = *static_cast<int*>(arg2);
         Ssl::Bio *bio;
-        if (arg1 == Ssl::Bio::BIO_TO_SERVER)
+        if (arg1 == Security::Io::BIO_TO_SERVER)
             bio = new Ssl::ServerBio(fd);
         else
             bio = new Ssl::ClientBio(fd);
-        assert(!table->ptr);
-        table->ptr = bio;
-        table->init = 1;
+        assert(!BIO_get_data(table));
+        BIO_set_data(table, bio);
+        BIO_set_init(table, 1);
         return 0;
     }
 
     case BIO_C_GET_FD:
-        if (table->init) {
-            Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
+        if (BIO_get_init(table)) {
+            Ssl::Bio *bio = static_cast<Ssl::Bio*>(BIO_get_data(table));
             assert(bio);
             if (arg2)
                 *static_cast<int*>(arg2) = bio->fd();
@@ -609,8 +643,8 @@ squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2)
         return 0;
 
     case BIO_CTRL_FLUSH:
-        if (table->init) {
-            Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr);
+        if (BIO_get_init(table)) {
+            Ssl::Bio *bio = static_cast<Ssl::Bio*>(BIO_get_data(table));
             assert(bio);
             bio->flush(table);
             return 1;
@@ -640,561 +674,65 @@ static void
 squid_ssl_info(const SSL *ssl, int where, int ret)
 {
     if (BIO *table = SSL_get_rbio(ssl)) {
-        if (Ssl::Bio *bio = static_cast<Ssl::Bio*>(table->ptr))
+        if (Ssl::Bio *bio = static_cast<Ssl::Bio*>(BIO_get_data(table)))
             bio->stateChanged(ssl, where, ret);
     }
 }
 
-Ssl::Bio::sslFeatures::sslFeatures():
-    sslHelloVersion(-1),
-    sslVersion(-1),
-    compressMethod(-1),
-    helloMsgSize(0),
-    unknownCiphers(false),
-    doHeartBeats(true),
-    tlsTicketsExtension(false),
-    hasTlsTicket(false),
-    tlsStatusRequest(false),
-    hasCcsOrNst(false),
-    initialized_(false)
-{
-    memset(client_random, 0, SSL3_RANDOM_SIZE);
-}
-
-int Ssl::Bio::sslFeatures::toSquidSSLVersion() const
-{
-    if (sslVersion == SSL2_VERSION)
-        return 2;
-    else if (sslVersion == SSL3_VERSION)
-        return 3;
-    else if (sslVersion == TLS1_VERSION)
-        return 4;
-#if OPENSSL_VERSION_NUMBER >= 0x10001000L
-    else if (sslVersion == TLS1_1_VERSION)
-        return 5;
-    else if (sslVersion == TLS1_2_VERSION)
-        return 6;
-#endif
-    else
-        return 1;
-}
-
-bool
-Ssl::Bio::sslFeatures::get(const SSL *ssl)
-{
-    sslVersion = SSL_version(ssl);
-    debugs(83, 7, "SSL version: " << SSL_get_version(ssl) << " (" << sslVersion << ")");
-
-#if defined(TLSEXT_NAMETYPE_host_name)
-    if (const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))
-        serverName = server;
-    debugs(83, 7, "SNI server name: " << serverName);
-#endif
-
-#if !defined(OPENSSL_NO_COMP)
-    if (ssl->session->compress_meth)
-        compressMethod = ssl->session->compress_meth;
-    else if (sslVersion >= 3) //if it is 3 or newer version then compression is disabled
-#endif
-        compressMethod = 0;
-    debugs(83, 7, "SSL compression: " << compressMethod);
-
-    STACK_OF(SSL_CIPHER) * ciphers = NULL;
-    if (ssl->server)
-        ciphers = ssl->session->ciphers;
-    else
-        ciphers = ssl->cipher_list;
-    if (ciphers) {
-        for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) {
-            SSL_CIPHER *c = sk_SSL_CIPHER_value(ciphers, i);
-            if (c != NULL) {
-                if (!clientRequestedCiphers.empty())
-                    clientRequestedCiphers.append(":");
-                clientRequestedCiphers.append(c->name);
-            }
-        }
-    }
-    debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
-
-    if (sslVersion >=3 && ssl->s3 && ssl->s3->client_random[0]) {
-        memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE);
-    }
-
-#if 0 /* XXX: OpenSSL 0.9.8k lacks at least some of these tlsext_* fields */
-    //The following extracted for logging purpuses:
-    // TLSEXT_TYPE_ec_point_formats
-    unsigned char *p;
-    int len;
-    if (ssl->server) {
-        p = ssl->session->tlsext_ecpointformatlist;
-        len = ssl->session->tlsext_ecpointformatlist_length;
-    } else {
-        p = ssl->tlsext_ecpointformatlist;
-        len = ssl->tlsext_ecpointformatlist_length;
-    }
-    if (p) {
-        ecPointFormatList = objToString(p, len);
-        debugs(83, 7, "tlsExtension ecPointFormatList of length " << len << " :" << ecPointFormatList);
-    }
-
-    // TLSEXT_TYPE_elliptic_curves
-    if (ssl->server) {
-        p = ssl->session->tlsext_ellipticcurvelist;
-        len = ssl->session->tlsext_ellipticcurvelist_length;
-    } else {
-        p = ssl->tlsext_ellipticcurvelist;
-        len = ssl->tlsext_ellipticcurvelist_length;
-    }
-    if (p) {
-        ellipticCurves = objToString(p, len);
-        debugs(83, 7, "tlsExtension ellipticCurveList of length " <<  len <<" :" << ellipticCurves);
-    }
-    // TLSEXT_TYPE_opaque_prf_input
-    p = NULL;
-    if (ssl->server) {
-        if (ssl->s3 &&  ssl->s3->client_opaque_prf_input) {
-            p = (unsigned char *)ssl->s3->client_opaque_prf_input;
-            len = ssl->s3->client_opaque_prf_input_len;
-        }
-    } else {
-        p = (unsigned char *)ssl->tlsext_opaque_prf_input;
-        len = ssl->tlsext_opaque_prf_input_len;
-    }
-    if (p) {
-        debugs(83, 7, "tlsExtension client-opaque-prf-input of length " << len);
-        opaquePrf = objToString(p, len);
-    }
-#endif
-    initialized_ = true;
-    return true;
-}
-
-int
-Ssl::Bio::sslFeatures::parseMsgHead(const MemBuf &buf)
-{
-    const unsigned char *head = (const unsigned char *)buf.content();
-    const char *s = objToString(head, buf.contentSize());
-    debugs(83, 7, "SSL Header: " << s);
-    if (buf.contentSize() < 5)
-        return 0;
-
-    if (helloMsgSize > 0)
-        return helloMsgSize;
-
-    // Check for SSLPlaintext/TLSPlaintext record
-    // RFC6101 section 5.2.1
-    // RFC5246 section 6.2.1
-    if (head[0] == 0x16) {
-        debugs(83, 7, "SSL version 3 handshake message");
-        // The SSL version exist in the 2nd and 3rd bytes
-        sslHelloVersion = (head[1] << 8) | head[2];
-        debugs(83, 7, "SSL Version :" << std::hex << std::setw(8) << std::setfill('0') << sslVersion);
-        // The hello message size exist in 4th and 5th bytes
-        helloMsgSize = (head[3] << 8) + head[4];
-        debugs(83, 7, "SSL Header Size: " << helloMsgSize);
-        helloMsgSize +=5;
-    } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) {
-        debugs(83, 7, "SSL version 2 handshake message with v3 support");
-        sslHelloVersion = 0x0002;
-        debugs(83, 7, "SSL Version :" << std::hex << std::setw(8) << std::setfill('0') << sslVersion);
-        // The hello message size exist in 2nd byte
-        helloMsgSize = head[1];
-        helloMsgSize +=2;
-    } else {
-        debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)");
-        return (helloMsgSize = -1);
-    }
-
-    // Set object as initialized. Even if we did not full parsing yet
-    // The basic features, like the SSL version is set
-    initialized_ = true;
-    return helloMsgSize;
-}
-
-bool
-Ssl::Bio::sslFeatures::checkForCcsOrNst(const unsigned char *msg, size_t size)
-{
-    while (size > 5) {
-        const int msgType = msg[0];
-        const int msgSslVersion = (msg[1] << 8) | msg[2];
-        debugs(83, 7, "SSL Message Version :" << std::hex << std::setw(8) << std::setfill('0') << msgSslVersion);
-        // Check for Change Cipher Spec message
-        // RFC5246 section 6.2.1
-        if (msgType == 0x14) {// Change Cipher Spec message found
-            debugs(83, 7, "SSL  Change Cipher Spec message found");
-            return true;
-        }
-        // Check for New Session Ticket message
-        // RFC5077 section 3.3
-        if (msgType == 0x04) {// New Session Ticket message found
-            debugs(83, 7, "TLS  New Session Ticket message found");
-            return true;
-        }
-        // The hello message size exist in 4th and 5th bytes
-        size_t msgLength = (msg[3] << 8) + msg[4];
-        debugs(83, 7, "SSL Message Size: " << msgLength);
-        msgLength += 5;
-
-        if (msgLength <= size) {
-            msg += msgLength;
-            size -= msgLength;
-        } else
-            size = 0;
-    }
-    return false;
-}
-
-bool
-Ssl::Bio::sslFeatures::get(const MemBuf &buf, bool record)
-{
-    int msgSize;
-    if ((msgSize = parseMsgHead(buf)) <= 0) {
-        debugs(83, 7, "Not a known SSL handshake message");
-        return false;
-    }
-
-    if (msgSize > buf.contentSize()) {
-        debugs(83, 2, "Partial SSL handshake message, can not parse!");
-        return false;
-    }
-
-    if (record) {
-        helloMessage.clear();
-        helloMessage.append(buf.content(), buf.contentSize());
-    }
-
-    const unsigned char *msg = (const unsigned char *)buf.content();
-    if (msg[0] & 0x80)
-        return parseV23Hello(msg, (size_t)msgSize);
-    else {
-        // Hello messages require 5 bytes header + 1 byte Msg type + 3 bytes for Msg size
-        if (buf.contentSize() < 9)
-            return false;
-
-        // Check for the Handshake/Message type
-        // The type 2 is a ServerHello, the type 1 is a ClientHello
-        // RFC5246 section 7.4
-        if (msg[5] == 0x2) { // ServerHello message
-            if (parseV3ServerHello(msg, (size_t)msgSize)) {
-                hasCcsOrNst = checkForCcsOrNst(msg + msgSize,  buf.contentSize() - msgSize);
-                return true;
-            }
-        } else if (msg[5] == 0x1) // ClientHello message,
-            return parseV3Hello(msg, (size_t)msgSize);
-    }
-
-    return false;
-}
-
-bool
-Ssl::Bio::sslFeatures::parseV3ServerHello(const unsigned char *messageContainer, size_t messageContainerSize)
-{
-    // Parse a ServerHello Handshake message
-    // RFC5246 section 7.4, 7.4.1.3
-    // The ServerHello starts at messageContainer + 5
-    const unsigned char *serverHello = messageContainer + 5;
-
-    // The Length field (bytes 1-3) plus 4 bytes of the serverHello message header (1 handshake type + 3 hello length)
-    const size_t helloSize = ((serverHello[1] << 16) | (serverHello[2] << 8) | serverHello[3]) + 4;
-    debugs(83, 7, "ServerHello message size: " << helloSize);
-    if (helloSize > messageContainerSize) {
-        debugs(83, 2, "ServerHello parse error");
-        return false;
-    }
-
-    // helloSize should be at least 38 bytes long:
-    // (SSL Version + Random + SessionId Length + Cipher Suite + Compression Method)
-    if (helloSize < 38) {
-        debugs(83, 2, "Too short ServerHello message");
-        return false;
-    }
-
-    debugs(83, 7, "Get fake features from v3 ServerHello message.");
-    // Get the correct version of the sub-hello message
-    sslVersion = (serverHello[4] << 8) | serverHello[5];
-    // At the position 38 (HelloHeader (6bytes) + SSL3_RANDOM_SIZE (32bytes))
-    const size_t sessIdLen = static_cast<size_t>(serverHello[38]);
-    debugs(83, 7, "Session ID Length: " <<  sessIdLen);
-
-    // The size should be enough to hold at least the following
-    // 4 (hello header)
-    // + 2 (SSL Version) + 32 (random) + 1 (sessionId length)
-    // + sessIdLength + 2 (cipher suite) + 1 (compression method)
-    // = 42 + sessIdLength
-    if (42 + sessIdLen > helloSize) {
-        debugs(83, 2, "ciphers length parse error");
-        return false;
-    }
-
-    // The sessionID stored at 39 position, after sessionID length field
-    sessionId.assign(reinterpret_cast<const char *>(serverHello + 39), sessIdLen);
-
-    // Check if there are extensions in hello message
-    // RFC5246 section 7.4.1.4
-    if (helloSize > 42 + sessIdLen + 2) {
-        // 42 + sessIdLen
-        const unsigned char *pToExtensions = serverHello + 42 + sessIdLen;
-        const size_t extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1];
-        // Check if the hello size can hold extensions
-        if (42 + 2 + sessIdLen + extensionsLen > helloSize ) {
-            debugs(83, 2, "Extensions length parse error");
-            return false;
-        }
-
-        pToExtensions += 2;
-        const unsigned char *ext = pToExtensions;
-        while (ext + 4 <= pToExtensions + extensionsLen) {
-            const size_t extType = (ext[0] << 8) | ext[1];
-            ext += 2;
-            const size_t extLen = (ext[0] << 8) | ext[1];
-            ext += 2;
-            debugs(83, 7, "TLS Extension: " << std::hex << extType << " of size:" << extLen);
-            // SessionTicket TLS Extension, RFC5077 section 3.2
-            if (extType == 0x23) {
-                tlsTicketsExtension = true;
-            }
-            ext += extLen;
-        }
-    }
-    return true;
-}
-
-bool
-Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *messageContainer, size_t messageContainerSize)
-{
-    // Parse a ClientHello Handshake message
-    // RFC5246 section 7.4, 7.4.1.2
-    // The ClientHello starts at messageContainer + 5
-    const unsigned char * clientHello = messageContainer + 5;
-
-    debugs(83, 7, "Get fake features from v3 ClientHello message.");
-    // The Length field (bytes 1-3) plus 4 bytes of the clientHello message header (1 handshake type + 3 hello length)
-    const size_t helloSize = ((clientHello[1] << 16) | (clientHello[2] << 8) | clientHello[3]) + 4;
-    debugs(83, 7, "ClientHello message size: " << helloSize);
-    if (helloSize > messageContainerSize) {
-        debugs(83, 2, "ClientHello parse error");
-        return false;
-    }
-
-    // helloSize should be at least 38 bytes long:
-    // (SSL Version(2) + Random(32) + SessionId Length(1) + Cipher Suite Length(2) + Compression Method Length(1))
-    if (helloSize < 38) {
-        debugs(83, 2, "Too short ClientHello message");
-        return false;
-    }
-
-    //For SSLv3 or TLSv1.* protocols we can get some more informations
-    if (messageContainer[1] != 0x3 || clientHello[0] != 0x1 /*HELLO A message*/) {
-        debugs(83, 2, "Not an SSLv3/TLSv1.x client hello message, stop parsing here");
-        return true;
-    }
-
-    // Get the correct version of the sub-hello message
-    sslVersion = (clientHello[4] << 8) | clientHello[5];
-    //Get Client Random number. It starts on the position 6 of clientHello message
-    memcpy(client_random, clientHello + 6, SSL3_RANDOM_SIZE);
-    debugs(83, 7, "Client random: " <<  objToString(client_random, SSL3_RANDOM_SIZE));
-
-    // At the position 38 (6+SSL3_RANDOM_SIZE)
-    const size_t sessIDLen = static_cast<size_t>(clientHello[38]);
-    debugs(83, 7, "Session ID Length: " <<  sessIDLen);
-
-    // The helloSize should be enough to hold at least the following
-    // 1 handshake type + 3 hello Length
-    // + 2 (SSL Version) + 32 (random) + 1 (sessionId length)
-    // + sessIdLength + 2 (cipher suite length) + 1 (compression method length)
-    // = 42 + sessIdLength
-    if (42 + sessIDLen > helloSize) {
-        debugs(83, 2, "Session ID length parse error");
-        return false;
-    }
-
-    // The sessionID stored art 39 position, after sessionID length field
-    sessionId.assign(reinterpret_cast<const char *>(clientHello + 39), sessIDLen);
-
-    //Ciphers list. It is stored after the Session ID.
-    // It is a variable-length vector(RFC5246 section 4.3)
-    const unsigned char *ciphers = clientHello + 39 + sessIDLen;
-    const size_t ciphersLen = (ciphers[0] << 8) | ciphers[1];
-    if (42 + sessIDLen + ciphersLen > helloSize) {
-        debugs(83, 2, "ciphers length parse error");
-        return false;
-    }
-
-    ciphers += 2;
-    if (ciphersLen) {
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-        const SSL_METHOD *method = TLS_method();
-#else
-        const SSL_METHOD *method = SSLv23_method();
-#endif
-        for (size_t i = 0; i < ciphersLen; i += 2) {
-            // each cipher in v3/tls  HELLO message is of size 2
-            const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i));
-            if (c != NULL) {
-                if (!clientRequestedCiphers.empty())
-                    clientRequestedCiphers.append(":");
-                clientRequestedCiphers.append(c->name);
-            } else
-                unknownCiphers = true;
-        }
-    }
-    debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
-
-    // Compression field: 1 bytes the number of compression methods and
-    // 1 byte for each compression method
-    const unsigned char *compression = ciphers + ciphersLen;
-    if (compression[0] > 1)
-        compressMethod = 1;
-    else
-        compressMethod = 0;
-    debugs(83, 7, "SSL compression methods number: " << static_cast<int>(compression[0]));
-
-    // Parse Extensions, RFC5246 section 7.4.1.4
-    const unsigned char *pToExtensions = compression + 1 + static_cast<int>(compression[0]);
-    if ((size_t)((pToExtensions - clientHello) + 2) < helloSize) {
-        const size_t extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1];
-        if ((pToExtensions - clientHello) + 2 + extensionsLen > helloSize) {
-            debugs(83, 2, "Extensions length parse error");
-            return false;
-        }
-
-        pToExtensions += 2;
-        const unsigned char *ext = pToExtensions;
-        while (ext + 4 <= pToExtensions + extensionsLen) {
-            const size_t extType = (ext[0] << 8) | ext[1];
-            ext += 2;
-            const size_t extLen = (ext[0] << 8) | ext[1];
-            ext += 2;
-            debugs(83, 7, "TLS Extension: " << std::hex << extType << " of size:" << extLen);
-
-            if (ext + extLen > pToExtensions + extensionsLen) {
-                debugs(83, 2, "Extension " << std::hex << extType << " length parser error");
-                return false;
-            }
-
-            //The SNI extension has the type 0 (extType == 0)
-            // RFC6066 sections 3, 10.2
-            // The two first bytes indicates the length of the SNI data (should be extLen-2)
-            // The next byte is the hostname type, it should be '0' for normal hostname (ext[2] == 0)
-            // The 3rd and 4th bytes are the length of the hostname
-            if (extType == 0 && ext[2] == 0) {
-                const size_t hostLen = (ext[3] << 8) | ext[4];
-                if (hostLen < extLen)
-                    serverName.assign(reinterpret_cast<const char *>(ext+5), hostLen);
-                debugs(83, 7, "Found server name: " << serverName);
-            } else if (extType == 15 && ext[0] != 0) {
-                // The heartBeats are the type 15, RFC6520
-                doHeartBeats = true;
-            } else if (extType == 0x23) {
-                //SessionTicket TLS Extension RFC5077
-                tlsTicketsExtension = true;
-                if (extLen != 0)
-                    hasTlsTicket = true;
-            } else if (extType == 0x05) {
-                // RFC6066 sections 8, 10.2
-                tlsStatusRequest = true;
-            } else if (extType == 0x3374) {
-                // detected TLS next protocol negotiate extension
-            } else if (extType == 0x10) {
-                // Application-Layer Protocol Negotiation Extension, RFC7301
-                const size_t listLen = (ext[0] << 8) | ext[1];
-                if (listLen < extLen)
-                    tlsAppLayerProtoNeg.assign(reinterpret_cast<const char *>(ext+5), listLen);
-            } else
-                extensions.push_back(extType);
-
-            ext += extLen;
-        }
-    }
-    return true;
-}
-
-bool
-Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello, size_t size)
-{
-    debugs(83, 7, "Get fake features from v23 ClientHello message.");
-    if (size < 7)
-        return false;
-
-    // Get the SSL/TLS version supported by client
-    sslVersion = (hello[3] << 8) | hello[4];
-
-    //Ciphers list. It is stored after the Session ID.
-    const unsigned int ciphersLen = (hello[5] << 8) | hello[6];
-    const unsigned char *ciphers = hello + 11;
-
-    if (size < ciphersLen + 11)
-        return false;
-
-    if (ciphersLen) {
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-        const SSL_METHOD *method = TLS_method();
-#else
-        const SSL_METHOD *method = SSLv23_method();
-#endif
-        for (unsigned int i = 0; i < ciphersLen; i += 3) {
-            // The v2 hello messages cipher has 3 bytes.
-            // The v2 cipher has the first byte not null
-            // Because we are going to sent only v3 message we
-            // are ignoring these ciphers
-            if (ciphers[i] != 0)
-                continue;
-            const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i + 1));
-            if (c != NULL) {
-                if (!clientRequestedCiphers.empty())
-                    clientRequestedCiphers.append(":");
-                clientRequestedCiphers.append(c->name);
-            }
-        }
-    }
-    debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
-
-    const unsigned int sessionIdLength = (hello[7] << 8) | hello[8];
-    debugs(83, 7, "SessionID length: " << sessionIdLength);
-    // SessionID starts at: hello+11+ciphersLen
-    if (sessionIdLength)
-        sessionId.assign((const char *)(hello + 11 + ciphersLen), sessionIdLength);
-
-    const unsigned int challengeLength = (hello[5] << 9) | hello[10];
-    debugs(83, 7, "Challenge Length: " << challengeLength);
-    //challenge starts at: hello+11+ciphersLen+sessionIdLength
-
-    compressMethod = 0;
-    return true;
-}
-
 void
-Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const
+applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl::BumpMode bumpMode)
 {
     // To increase the possibility for bumping after peek mode selection or
     // splicing after stare mode selection it is good to set the
     // SSL protocol version.
-    // The SSL_set_ssl_method is not the correct method because it will strict
-    // SSL version which can be used to the SSL version used for client hello message.
+    // The SSL_set_ssl_method is wrong here because it will restrict the
+    // permitted transport version to be identical to the version used in the
+    // ClientHello message.
     // For example will prevent comunnicating with a tls1.0 server if the
     // client sent and tlsv1.2 Hello message.
 #if defined(TLSEXT_NAMETYPE_host_name)
-    if (!serverName.isEmpty()) {
-        SSL_set_tlsext_host_name(ssl, serverName.c_str());
+    if (!details->serverName.isEmpty()) {
+        SSL_set_tlsext_host_name(ssl, details->serverName.c_str());
     }
 #endif
-    if (!clientRequestedCiphers.empty())
-        SSL_set_cipher_list(ssl, clientRequestedCiphers.c_str());
+
+    if (!details->ciphers.empty()) {
+        SBuf strCiphers;
+        for (auto cipherId: details->ciphers) {
+            unsigned char cbytes[3];
+            cbytes[0] = (cipherId >> 8) & 0xFF;
+            cbytes[1] = cipherId & 0xFF;
+            cbytes[2] = 0;
+            if (const auto c = SSL_CIPHER_find(ssl, cbytes)) {
+                if (!strCiphers.isEmpty())
+                    strCiphers.append(":");
+                strCiphers.append(SSL_CIPHER_get_name(c));
+            }
+        }
+        if (!strCiphers.isEmpty())
+            SSL_set_cipher_list(ssl, strCiphers.c_str());
+    }
+
 #if defined(SSL_OP_NO_COMPRESSION) /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */
-    if (compressMethod == 0)
+    if (!details->compressionSupported)
         SSL_set_options(ssl, SSL_OP_NO_COMPRESSION);
 #endif
 
+#if defined(SSL_OP_NO_TLSv1_3)
+    // avoid "inappropriate fallback" OpenSSL error messages
+    if (details->tlsSupportedVersion && Security::Tls1p2orEarlier(details->tlsSupportedVersion))
+        SSL_set_options(ssl, SSL_OP_NO_TLSv1_3);
+#endif
+
 #if defined(TLSEXT_STATUSTYPE_ocsp)
-    if (tlsStatusRequest)
+    if (details->tlsStatusRequest)
         SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
 #endif
 
 #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation)
-    if (!tlsAppLayerProtoNeg.isEmpty()) {
+    if (!details->tlsAppLayerProtoNeg.isEmpty()) {
         if (bumpMode == Ssl::bumpPeek)
-            SSL_set_alpn_protos(ssl, (const unsigned char*)tlsAppLayerProtoNeg.rawContent(), tlsAppLayerProtoNeg.length());
+            SSL_set_alpn_protos(ssl, (const unsigned char*)details->tlsAppLayerProtoNeg.rawContent(), details->tlsAppLayerProtoNeg.length());
         else {
             static const unsigned char supported_protos[] = {8, 'h','t','t', 'p', '/', '1', '.', '1'};
             SSL_set_alpn_protos(ssl, supported_protos, sizeof(supported_protos));
@@ -1203,20 +741,5 @@ Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl, Ssl::BumpMode bumpMode) const
 #endif
 }
 
-std::ostream &
-Ssl::Bio::sslFeatures::print(std::ostream &os) const
-{
-    static std::string buf;
-    // TODO: Also print missing features like the HeartBeats and AppLayerProtoNeg
-    return os << "v" << sslVersion <<
-           " SNI:" << (serverName.isEmpty() ? SBuf("-") : serverName) <<
-           " comp:" << compressMethod <<
-           " Ciphers:" << clientRequestedCiphers <<
-           " Random:" << objToString(client_random, SSL3_RANDOM_SIZE) <<
-           " ecPointFormats:" << ecPointFormatList <<
-           " ec:" << ellipticCurves <<
-           " opaquePrf:" << opaquePrf;
-}
-
-#endif /* USE_SSL */
+#endif // USE_OPENSSL