/*
- * 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);
/* 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 = {
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;
}
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;
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);
allowSplice(false),
allowBump(false),
holdWrite_(false),
- holdRead_(true),
record_(false),
parsedHandshake(false),
parseError(false),
bumpMode_(bumpNone),
- rbufConsumePos(0)
+ rbufConsumePos(0),
+ parser_(Security::HandshakeParser::fromServer)
{
}
Ssl::ServerBio::setClientFeatures(Security::TlsDetails::Pointer const &details, SBuf const &aHello)
{
clientTlsDetails = details;
- clientHelloMessage = aHello;
+ clientSentHello = aHello;
};
int
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);
}
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;
}
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;
}
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);
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;
}
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;
}
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);
}
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);
}
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();
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;
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);
}
}
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())
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);