From: Christos Tsantilas Date: Thu, 31 Mar 2016 18:37:15 +0000 (+0300) Subject: First fast-sni implementation X-Git-Tag: SQUID_4_0_11~29^2~32 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3cae14a65aa6b5b0cba01d381224254c3c1a48d1;p=thirdparty%2Fsquid.git First fast-sni implementation Squid parses incomming client SSL hello mesage, before create any openSSL related structures and objects. After acl check at bumping step2. Actually creating openSSL objects for client side still can be delayed untill the server side is finishes. The only reason to create openSSL structures imediatelly after step2 is to use openSSL to check for unsupported comunications features and settings and fallback to spliceOnError. Regression: Squid does not parses client Hello message in the case of bump-server-first and bump-client-first. The supported and requested SSL versions (i %ssl::>received_supported_version and %ssl::>received_hello_version formating codes ) can not be logged for these modes. The code still needs cleanup. --- diff --git a/src/client_side.cc b/src/client_side.cc index c0de5520ee..b26f118af8 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -2186,6 +2186,22 @@ ConnStateData::clientParseRequests() void ConnStateData::afterClientRead() { +#if USE_OPENSSL + if (atTlsPeek) { + assert(!inBuf.isEmpty()); + if (!tlsParser.parseClientHello(inBuf)) { + if (!tlsParser.parseError) { + readSomeData(); + return; + } + } + atTlsPeek = false; + Must(sslServerBump); + Must(sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare); + startPeekAndSplice(); + return; + } +#endif /* Process next request */ if (pipeline.empty()) fd_note(clientConnection->fd, "Reading next request"); @@ -2420,6 +2436,7 @@ ConnStateData::ConnStateData(const MasterXaction::Pointer &xact) : switchedToHttps_(false), sslServerBump(NULL), signAlgorithm(Ssl::algSignTrusted), + atTlsPeek(false), #endif stoppedSending_(NULL), stoppedReceiving_(NULL) @@ -2601,11 +2618,11 @@ Squid_SSL_accept(ConnStateData *conn, PF *callback) switch (ssl_error) { case SSL_ERROR_WANT_READ: - Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0); + Comm::SetSelect(fd, COMM_SELECT_READ, callback, (callback != NULL ? conn : NULL), 0); return 0; case SSL_ERROR_WANT_WRITE: - Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0); + Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, (callback != NULL ? conn : NULL), 0); return 0; case SSL_ERROR_SYSCALL: @@ -3092,6 +3109,11 @@ ConnStateData::getSslContextDone(Security::ContextPtr sslContext, bool isNew) Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0); switchedToHttps_ = true; + auto ssl = fd_table[clientConnection->fd].ssl.get(); + BIO *b = SSL_get_rbio(ssl); + Ssl::ClientBio *bio = static_cast(b->ptr); + bio->setReadBufData(inBuf); + bio->parsedDetails(tlsParser.details); } void @@ -3123,7 +3145,19 @@ ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) } else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) { request->flags.sslPeek = true; sslServerBump = new Ssl::ServerBump(request, NULL, bumpServerMode); - startPeekAndSplice(); + + // commSetConnTimeout() was called for this request before we switched. + // Fix timeout to request_start_timeout + typedef CommCbMemFunT TimeoutDialer; + AsyncCall::Pointer timeoutCall = JobCallback(33, 5, + TimeoutDialer, this, ConnStateData::requestTimeout); + commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall); + // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have + // a bumbed "connect" request on non transparent port. + receivedFirstByte_ = false; + // Get more data to peek at Tls + atTlsPeek = true; + readSomeData(); return; } @@ -3148,77 +3182,29 @@ ConnStateData::spliceOnError(const err_type err) return false; } -/** negotiate an SSL connection */ -static void -clientPeekAndSpliceSSL(int fd, void *data) -{ - ConnStateData *conn = (ConnStateData *)data; - auto ssl = fd_table[fd].ssl.get(); - - debugs(83, 5, "Start peek and splice on FD " << fd); - int ret = 0; - if ((ret = Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) < 0) - debugs(83, 2, "SSL_accept failed."); - - BIO *b = SSL_get_rbio(ssl); - assert(b); - Ssl::ClientBio *bio = static_cast(b->ptr); - if (ret < 0) { - const err_type err = bio->noSslClient() ? ERR_PROTOCOL_UNKNOWN : ERR_SECURE_ACCEPT_FAIL; - if (!conn->spliceOnError(err)) - conn->clientConnection->close(); +void ConnStateData::startPeekAndSplice() +{ + if (tlsParser.parseError) { + if (!spliceOnError(ERR_PROTOCOL_UNKNOWN)) + clientConnection->close(); return; } + receivedFirstByte(); - if (!bio->rBufData().isEmpty() > 0) - conn->receivedFirstByte(); - - if (bio->gotHello()) { - if (conn->serverBump()) { - Security::TlsDetails::Pointer const &details = bio->receivedHelloDetails(); - if (!details->serverName.isEmpty()) { - conn->serverBump()->clientSni = details->serverName; - conn->resetSslCommonName(details->serverName.c_str()); - } + if (serverBump()) { + Security::TlsDetails::Pointer const &details = tlsParser.details; + if (!details->serverName.isEmpty()) { + serverBump()->clientSni = details->serverName; + resetSslCommonName(details->serverName.c_str()); } - - debugs(83, 5, "I got hello. Start forwarding the request!!! "); - Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0); - Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0); - conn->startPeekAndSpliceDone(); - return; } -} - -void ConnStateData::startPeekAndSplice() -{ - // will call httpsPeeked() with certificate and connection, eventually - auto unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port); - fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX; - if (!httpsCreate(clientConnection, unConfiguredCTX)) - return; - - // commSetConnTimeout() was called for this request before we switched. - // Fix timeout to request_start_timeout - typedef CommCbMemFunT TimeoutDialer; - AsyncCall::Pointer timeoutCall = JobCallback(33, 5, - TimeoutDialer, this, ConnStateData::requestTimeout); - commSetConnTimeout(clientConnection, Config.Timeout.request_start_timeout, timeoutCall); - // Also reset receivedFirstByte_ flag to allow this timeout work in the case we have - // a bumbed "connect" request on non transparent port. - receivedFirstByte_ = false; - - // Disable the client read handler until CachePeer selection is complete + // We should disable read/write handlers Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); - Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0); - switchedToHttps_ = true; + Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, NULL, NULL, 0); - auto ssl = fd_table[clientConnection->fd].ssl.get(); - BIO *b = SSL_get_rbio(ssl); - Ssl::ClientBio *bio = static_cast(b->ptr); - bio->hold(true); + startPeekAndSpliceDone(); } void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data) @@ -3252,32 +3238,38 @@ void ConnStateData::splice() { // normally we can splice here, because we just got client hello message - auto ssl = fd_table[clientConnection->fd].ssl.get(); - //retrieve received TLS client information - clientConnection->tlsNegotiations()->fillWith(ssl); + if (auto ssl = fd_table[clientConnection->fd].ssl.get()) { + // We built + //retrieve received TLS client information + clientConnection->tlsNegotiations()->fillWith(ssl); - BIO *b = SSL_get_rbio(ssl); - Ssl::ClientBio *bio = static_cast(b->ptr); - SBuf const &rbuf = bio->rBufData(); - debugs(83,5, "Bio for " << clientConnection << " read " << rbuf.length() << " helo bytes"); - // Do splice: - fd_table[clientConnection->fd].read_method = &default_read_method; - fd_table[clientConnection->fd].write_method = &default_write_method; + // BIO *b = SSL_get_rbio(ssl); + // Ssl::ClientBio *bio = static_cast(b->ptr); + // SBuf const &rbuf = bio->rBufData(); + // // The following do not needed, inBuf and rbuf has the same content. + // inBuf.assign(rbuf); + // debugs(83,5, "Bio for " << clientConnection << " read " << rbuf.length() << " helo bytes"); + + // Do splice: + fd_table[clientConnection->fd].read_method = &default_read_method; + fd_table[clientConnection->fd].write_method = &default_write_method; + + } else { + clientConnection->tlsNegotiations()->fillWith(tlsParser.details); + } if (transparent()) { // set the current protocol to something sensible (was "HTTPS" for the bumping process) // we are sending a faked-up HTTP/1.1 message wrapper, so go with that. transferProtocol = Http::ProtocolVersion(); // XXX: copy from MemBuf reallocates, not a regression since old code did too - fakeAConnectRequest("intercepted TLS spliced", rbuf); + fakeAConnectRequest("intercepted TLS spliced", inBuf); } else { // XXX: assuming that there was an HTTP/1.1 CONNECT to begin with... // reset the current protocol to HTTP/1.1 (was "HTTPS" for the bumping process) transferProtocol = Http::ProtocolVersion(); - // inBuf still has the "CONNECT ..." request data, reset it to SSL hello message - inBuf.append(rbuf); Http::StreamPointer context = pipeline.front(); ClientHttpRequest *http = context->http; tunnelStart(http); @@ -3307,6 +3299,39 @@ ConnStateData::startPeekAndSpliceDone() return; } + // will call httpsPeeked() with certificate and connection, eventually + auto unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port); + fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX; + + if (!httpsCreate(clientConnection, unConfiguredCTX)) + return; + + switchedToHttps_ = true; + + auto ssl = fd_table[clientConnection->fd].ssl.get(); + BIO *b = SSL_get_rbio(ssl); + Ssl::ClientBio *bio = static_cast(b->ptr); + bio->setReadBufData(inBuf); + bio->parsedDetails(tlsParser.details); + bio->hold(true); + + // Here squid should have all of the client hello message so the + // Squid_SSL_accept should return 0; + // This block exist only to force openSSL parse client hello and detect + // ERR_SECURE_ACCEPT_FAIL error, which should be checked and splice if required. + int ret = 0; + if ((ret = Squid_SSL_accept(this, NULL)) < 0) { + debugs(83, 2, "SSL_accept failed."); + const err_type err = ERR_SECURE_ACCEPT_FAIL; + if (!spliceOnError(err)) + clientConnection->close(); + return; + } + + // Do we need to reset inBuf here? + // inBuf.clear(); + + debugs(83, 5, "Peek and splice at step2 done. Start forwarding the request!!! "); FwdState::Start(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw(), http ? http->al : NULL); } diff --git a/src/client_side.h b/src/client_side.h index fc7a7a0898..d34f6b9ceb 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -24,6 +24,7 @@ #include "auth/UserRequest.h" #endif #if USE_OPENSSL +#include "security/Handshake.h" #include "ssl/support.h" #endif @@ -254,7 +255,7 @@ public: bool serveDelayedError(Http::Stream *); Ssl::BumpMode sslBumpMode; ///< ssl_bump decision (Ssl::bumpEnd if n/a). - + Security::HandshakeParser tlsParser; #else bool switchedToHttps() const { return false; } #endif @@ -358,6 +359,7 @@ private: /// HTTPS server cert. fetching state for bump-ssl-server-first Ssl::ServerBump *sslServerBump; Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use + bool atTlsPeek; #endif /// the reason why we no longer write the response or nil diff --git a/src/security/Handshake.cc b/src/security/Handshake.cc index 747d0bd9ac..afac430347 100644 --- a/src/security/Handshake.cc +++ b/src/security/Handshake.cc @@ -398,15 +398,20 @@ Security::HandshakeParser::parseServerHelloHandshakeMessage(const SBuf &raw) { BinaryTokenizer tkHsk(raw); Must(details); - - details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion"); - tkHsk.commit(); - details->clientRandom = tkHsk.area(SQUID_TLS_RANDOM_SIZE, "Client Random"); - tkHsk.commit(); - P8String session(tkHsk, "Session ID"); - details->sessionId = session.body; - P16String extensions(tkHsk, "Extensions List"); - parseExtensions(extensions.body); +#if 0 // Always retrieve details, enough fast operation + if (::Config.onoff.logTlsServerHelloDetails) { +#endif + details->tlsSupportedVersion = tkHsk.uint16("tlsSupportedVersion"); + tkHsk.commit(); + details->clientRandom = tkHsk.area(SQUID_TLS_RANDOM_SIZE, "Client Random"); + tkHsk.commit(); + P8String session(tkHsk, "Session ID"); + details->sessionId = session.body; + P16String extensions(tkHsk, "Extensions List"); + parseExtensions(extensions.body); +#if 0 + } +#endif } void diff --git a/src/security/NegotiationHistory.cc b/src/security/NegotiationHistory.cc index d990610470..65e5a8e164 100644 --- a/src/security/NegotiationHistory.cc +++ b/src/security/NegotiationHistory.cc @@ -63,16 +63,8 @@ Security::NegotiationHistory::fillWith(Security::SessionPtr ssl) BIO *b = SSL_get_rbio(ssl); Ssl::Bio *bio = static_cast(b->ptr); - - if (::Config.onoff.logTlsServerHelloDetails) { - //PRobably move this if inside HandhakeParser - // if (Ssl::ServerBio *srvBio = dynamic_cast(bio)) - // srvBio->extractHelloFeatures(); - } - - const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails(); - helloVersion_ = details->tlsVersion; - supportedVersion_ = details->tlsSupportedVersion; + if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails()) + fillWith(details); debugs(83, 5, "SSL connection info on FD " << bio->fd() << " SSL version " << version_ << @@ -80,6 +72,13 @@ Security::NegotiationHistory::fillWith(Security::SessionPtr ssl) #endif } +void +Security::NegotiationHistory::fillWith(Security::TlsDetails::Pointer const &details) +{ + helloVersion_ = details->tlsVersion; + supportedVersion_ = details->tlsSupportedVersion; +} + const char * Security::NegotiationHistory::cipherName() const { diff --git a/src/security/NegotiationHistory.h b/src/security/NegotiationHistory.h index 18f4d313f0..44edaf5a1a 100644 --- a/src/security/NegotiationHistory.h +++ b/src/security/NegotiationHistory.h @@ -10,6 +10,7 @@ #define SQUID_SRC_SECURITY_NEGOTIATIONHISTORY_H #include "security/Session.h" +#include "security/Handshake.h" namespace Security { @@ -18,6 +19,7 @@ class NegotiationHistory public: NegotiationHistory(); void fillWith(Security::SessionPtr); ///< Extract negotiation information from TLS object + void fillWith(Security::TlsDetails::Pointer const &details); ///< Extract negotiation information from parser TlsDetails object const char *cipherName() const; ///< The name of negotiated cipher /// String representation of TLS negotiated version const char *negotiatedVersion() const {return printTlsVersion(version_);} diff --git a/src/ssl/PeekingPeerConnector.cc b/src/ssl/PeekingPeerConnector.cc index 89ec1a1dcc..9b2fe56591 100644 --- a/src/ssl/PeekingPeerConnector.cc +++ b/src/ssl/PeekingPeerConnector.cc @@ -146,19 +146,13 @@ Ssl::PeekingPeerConnector::initializeSsl() // or terminate client and server connections assert(clientConn != NULL); SBuf *hostName = NULL; - Ssl::ClientBio *cltBio = NULL; //Enable Status_request tls extension, required to bump some clients SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp); - // In server-first bumping mode, clientSsl is NULL. - if (auto clientSsl = fd_table[clientConn->fd].ssl.get()) { - BIO *b = SSL_get_rbio(clientSsl); - cltBio = static_cast(b->ptr); - const Security::TlsDetails::Pointer &details = cltBio->receivedHelloDetails(); - if (details != NULL && !details->serverName.isEmpty()) - hostName = new SBuf(details->serverName); - } + const Security::TlsDetails::Pointer details = csd->tlsParser.details; + if (details != NULL && !details->serverName.isEmpty()) + hostName = new SBuf(details->serverName); if (!hostName) { // While we are peeking at the certificate, we may not know the server @@ -174,8 +168,12 @@ Ssl::PeekingPeerConnector::initializeSsl() Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2); if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) { - assert(cltBio); - const Security::TlsDetails::Pointer &details = cltBio->receivedHelloDetails(); + auto clientSsl = fd_table[clientConn->fd].ssl.get(); + Must(clientSsl); + BIO *bc = SSL_get_rbio(clientSsl); + Ssl::ClientBio *cltBio = static_cast(bc->ptr); + Must(cltBio); + //const Security::TlsDetails::Pointer &details = csd->tlsParser.details; if (details->tlsVersion != -1) { applyTlsDetailsToSSL(ssl, details, csd->sslBumpMode); // Should we allow it for all protocols? diff --git a/src/ssl/bio.cc b/src/ssl/bio.cc index ef242d0cfa..506f062641 100644 --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@ -195,32 +195,19 @@ Ssl::ClientBio::write(const char *buf, int size, BIO *table) int Ssl::ClientBio::read(char *buf, int size, BIO *table) { - if (!parser_.parseDone) { - int bytes = readAndBuffer(table, "TLS client Hello"); - if (bytes <= 0) - return bytes; - if (!parser_.parseClientHello(rbuf)) { - if (!parser_.parseError) - BIO_set_retry_read(table); - return -1; - } - } - if (holdRead_) { debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)"); BIO_set_retry_read(table); return -1; } - if (parser_.parseDone) { - 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); - } + 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; } diff --git a/src/ssl/bio.h b/src/ssl/bio.h index 8182f0e950..b94cd79386 100644 --- a/src/ssl/bio.h +++ b/src/ssl/bio.h @@ -52,6 +52,9 @@ public: /// appears, or an error occurs. See SSL_set_info_callback(). virtual void stateChanged(const SSL *ssl, int where, int ret); + /// Return the TLS Details advertised by TLS server. + virtual const Security::TlsDetails::Pointer &receivedHelloDetails() const = 0; + /// Creates a low-level BIO table, creates a high-level Ssl::Bio object /// for a given socket, and then links the two together via BIO_C_SET_FD. static BIO *Create(const int fd, Type type); @@ -61,16 +64,10 @@ public: /// Reads data from socket and record them to a buffer int readAndBuffer(BIO *table, const char *description); - /// Return the TLS Details requested/advirised by TLS client or server. - const Security::TlsDetails::Pointer &receivedHelloDetails() const {return parser_.details;} - const SBuf &rBufData() {return rbuf;} protected: const int fd_; ///< the SSL socket we are reading and writing SBuf rbuf; ///< Used to buffer input data. - /// The features retrieved from client or Server TLS hello message - //Security::TlsDetails::Pointer receivedHelloDetails_; - Security::HandshakeParser parser_; ///< The SSL messages parser. }; /// BIO node to handle socket IO for squid client side @@ -92,18 +89,21 @@ public: /// to socket and sets the "read retry" flag of the BIO to true virtual int read(char *buf, int size, BIO *table); /// Return true if the client hello message received and analized - bool gotHello() const { return (parser_.parseDone && !parser_.parseError); } + //bool gotHello() const { return (parser_.parseDone && !parser_.parseError); } /// Prevents or allow writting on socket. void hold(bool h) {holdRead_ = holdWrite_ = h;} - /// True if client does not looks like an SSL client - bool noSslClient() {return parser_.parseError;} + void setReadBufData(SBuf &data) {rbuf = data;} + const Security::TlsDetails::Pointer &receivedHelloDetails() const {return details;} + void parsedDetails(Security::TlsDetails::Pointer &someDetails) {details = someDetails;} private: /// True if the SSL state corresponds to a hello message bool isClientHello(int state); bool holdRead_; ///< The read hold state of the bio. bool holdWrite_; ///< The write hold state of the bio. int helloSize; ///< The SSL hello message sent by client size - //bool wrongProtocol; ///< true if client SSL hello parsing failed + + /// SSL client features extracted from ClientHello message or SSL object + Security::TlsDetails::Pointer details; }; /// BIO node to handle socket IO for squid server side @@ -165,6 +165,8 @@ public: void mode(Ssl::BumpMode m) {bumpMode_ = m;} Ssl::BumpMode bumpMode() {return bumpMode_;} ///< return the bumping mode + /// Return the TLS Details advertised by TLS server. + const Security::TlsDetails::Pointer &receivedHelloDetails() const {return parser_.details;} /// Return true if the Server hello message received bool gotHello() const { return (parser_.parseDone && !parser_.parseError); } @@ -190,6 +192,7 @@ private: ///< The size of data stored in rbuf which passed to the openSSL size_t rbufConsumePos; + Security::HandshakeParser parser_; ///< The SSL messages parser. }; } // namespace Ssl