]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ssl/bio.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / ssl / bio.cc
index c048d07f54c711049411545b554fc2ae9340e7be..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.
 #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);
@@ -42,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 = {
@@ -56,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)
+Ssl::Bio::Create(const int fd, Security::Io::Type type)
 {
-    if (BIO *bio = BIO_new(&SquidMethods)) {
+#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;
     }
@@ -147,27 +164,46 @@ Ssl::Bio::stateChanged(const SSL *ssl, int where, int ret)
            SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")");
 }
 
-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
-           );
+Ssl::ClientBio::ClientBio(const int anFd):
+    Bio(anFd),
+    holdRead_(false),
+    holdWrite_(false),
+    helloSize(0),
+    abortReason(nullptr)
+{
+    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;
@@ -179,6 +215,12 @@ Ssl::ClientBio::write(const char *buf, int size, BIO *table)
 int
 Ssl::ClientBio::read(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 (holdRead_) {
         debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)");
         BIO_set_retry_read(table);
@@ -203,12 +245,12 @@ Ssl::ServerBio::ServerBio(const int anFd):
     allowSplice(false),
     allowBump(false),
     holdWrite_(false),
-    holdRead_(true),
     record_(false),
     parsedHandshake(false),
     parseError(false),
     bumpMode_(bumpNone),
-    rbufConsumePos(0)
+    rbufConsumePos(0),
+    parser_(Security::HandshakeParser::fromServer)
 {
 }
 
@@ -222,7 +264,7 @@ void
 Ssl::ServerBio::setClientFeatures(Security::TlsDetails::Pointer const &details, SBuf const &aHello)
 {
     clientTlsDetails = details;
-    clientHelloMessage = aHello;
+    clientSentHello = aHello;
 };
 
 int
@@ -276,12 +318,6 @@ Ssl::ServerBio::readAndParse(char *buf, const int size, BIO *table)
         parseError = true;
     }
 
-    if (holdRead_) {
-         debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)");
-         BIO_set_retry_read(table);
-         return -1;
-    }
-
     return giveBuffered(buf, size);
 }
 
@@ -290,12 +326,12 @@ Ssl::ServerBio::readAndParse(char *buf, const int size, BIO *table)
 int
 Ssl::ServerBio::readAndBuffer(BIO *table)
 {
-    char *space = rbuf.rawSpace(SQUID_TCP_SO_RCVBUF);
-    const int result = Ssl::Bio::read(space, rbuf.spaceSize(), 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.forceSize(rbuf.length() + result);
+    rbuf.rawAppendFinish(space, result);
     return result;
 }
 
@@ -326,6 +362,9 @@ static bool
 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;
@@ -429,33 +468,37 @@ Ssl::ServerBio::write(const char *buf, int size, BIO *table)
     }
 
     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 (ssl) {
-                if (bumpMode_ == Ssl::bumpPeek) {
-                    if (adjustSSL(ssl, clientTlsDetails, clientHelloMessage))
-                        allowBump = true;
-                    allowSplice = true;
-                    helloMsg.append(clientHelloMessage);
-                    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, clientTlsDetails, clientHelloMessage)) {
-                        allowSplice = true;
-                        helloMsg.append(clientHelloMessage);
-                        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);
 
@@ -505,14 +548,26 @@ Ssl::ServerBio::resumingSession()
     return parser_.resumingSession;
 }
 
+bool
+Ssl::ServerBio::encryptedCertificates() const
+{
+    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;
 }
 
@@ -520,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;
 }
 
@@ -529,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);
 }
@@ -538,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);
 }
@@ -562,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();
@@ -588,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;
@@ -619,7 +674,7 @@ 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);
     }
 }
@@ -648,16 +703,10 @@ applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl
             cbytes[0] = (cipherId >> 8) & 0xFF;
             cbytes[1] = cipherId & 0xFF;
             cbytes[2] = 0;
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-            const SSL_METHOD *method = TLS_method();
-#else
-            const SSL_METHOD *method = SSLv23_method();
-#endif
-            const SSL_CIPHER *c = method->get_cipher_by_char(cbytes);
-            if (c != NULL) {
+            if (const auto c = SSL_CIPHER_find(ssl, cbytes)) {
                 if (!strCiphers.isEmpty())
                     strCiphers.append(":");
-                strCiphers.append(c->name);
+                strCiphers.append(SSL_CIPHER_get_name(c));
             }
         }
         if (!strCiphers.isEmpty())
@@ -669,6 +718,12 @@ applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl
         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 (details->tlsStatusRequest)
         SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);