From: Christos Tsantilas Date: Thu, 22 May 2014 12:00:50 +0000 (+0300) Subject: Peek and Splice: New ACL lists configuration X-Git-Tag: SQUID_3_5_0_1~89^2~18 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5d65362c32e3fd7e944b86379232d8e04720d923;p=thirdparty%2Fsquid.git Peek and Splice: New ACL lists configuration This patch: - removes ssl_bump_peeked access list - Defines three steps of the SSL bumping processing: step1: Get TCP-level and CONNECT info. Evaluate ssl_bump and perform the first matching action (splice, bump, peek, stare, terminate, or err) step2: Get SSL Client Hello info. Evaluate ssl_bump and perform the first matching action (splice, bump, peek, stare, terminate, or err). Peeking usually prevents future bumping. Staring usually prevents future splicing. step3: Get SSL Server Hello info. Evaluate ssl_bump and perform the first matching action (splice, bump, terminate, or err). In most cases, the only remaining choice at this step is whether to terminate the connection. The splicing or bumping decision is usually dictated by either peeking or staring at the previous step. - The ssl_bump ACLs list may evaluated in all SSL Bumping processing steps to take a decision for the next step: splice or none: Become a TCP tunnel without decoding the connection. bump: Establish a secure connection with the server and, using a mimicked server certificate, with the client peek: Receive client (step1) or server (step2) certificate while preserving the possibility of splicing the connection. Peeking at the server certificate usually precludes future bumping of the connection. stare: Receive client (step1) or server (step2) certificate while preserving the possibility of bumping the connection. Staring at the server certificate usually precludes future splicing of the connection. terminate or err: Close client and server connections. All actions except peek and stare correspond to final decisions: Once an ssl_bump directive with a final action matches, no further ssl_bump evaluations will take place, regardless of the current processing step. - Add the atstep acl to match against SSL bumping step: "step1", "step2" or "step3" Current Implementation details: --------------------------------- 1) If the "peek" mode selected in step2 then the client hello message forwarded to server. If this mode selected in step2 the splice is always possible and bump maybe is not possible (in most cases where the client uses different SSL client library implementation) 2) If the "stare" mode selected in step2 then the squid builds a new hello message, which try to mimic, if it is possible , client hello message. If stare selected in step2 the bump is always possible, but splice maybe is not possible any more. 3) In step3 if bump decided, and bump is not possible any more then squid is always splicing. 4) In step3 if splice decided but splice is not possible any more then squid is always bumping. 5) Because of (3) and (4), in practice, if firefox browser used with peek mode, squid always splice the connection, because squid/openSSL does not support the firefox SSL features reported in client hello message. 6) In step2 if ACL list evaluation result to terminate or err then we just close client connection. If the check result to ssl-bump then just bump. If check result to client-first, server-first, then bump the connection else do peek/stare. 7) In step3 the ssl_bump ACL list evakuation result client-first, server-first, bump or peek result to bumping (if bumping is possible). Example configurations: acl step1 atstep Step1 acl step2 atstep Step2 acl step3 atstep Step3 ssl_bump peek step1 all ssl_bump peek step2 all ssl_bump splice step3 all --- diff --git a/src/AclRegs.cc b/src/AclRegs.cc index b0700b939a..3a7cd16f5e 100644 --- a/src/AclRegs.cc +++ b/src/AclRegs.cc @@ -15,6 +15,10 @@ #include "acl/Arp.h" #include "acl/Eui64.h" #endif +#if USE_OPENSSL +#include "acl/AtBumpStep.h" +#include "acl/AtBumpStepData.h" +#endif #include "acl/Asn.h" #include "acl/Browser.h" #include "acl/Checklist.h" @@ -160,6 +164,9 @@ ACL::Prototype ACLCertificate::CARegistryProtoype(&ACLCertificate::CARegistryEnt ACLStrategised ACLCertificate::CARegistryEntry_(new ACLCertificateData (Ssl::GetX509CAAttribute, "*"), ACLCertificateStrategy::Instance(), "ca_cert"); ACL::Prototype ACLServerCertificate::X509FingerprintRegistryProtoype(&ACLServerCertificate::X509FingerprintRegistryEntry_, "server_cert_fingerprint"); ACLStrategised ACLServerCertificate::X509FingerprintRegistryEntry_(new ACLCertificateData(Ssl::GetX509Fingerprint, "-sha1", true), ACLServerCertificateStrategy::Instance(), "server_cert_fingerprint"); + +ACL::Prototype ACLAtStep::RegistryProtoype(&ACLAtStep::RegistryEntry_, "atstep"); +ACLStrategised ACLAtStep::RegistryEntry_(new ACLAtStepData, ACLAtStepStrategy::Instance(), "atstep"); #endif #if USE_SQUID_EUI diff --git a/src/SquidConfig.h b/src/SquidConfig.h index 30a6d53f10..97d09a3100 100644 --- a/src/SquidConfig.h +++ b/src/SquidConfig.h @@ -390,7 +390,6 @@ public: #if USE_OPENSSL acl_access *ssl_bump; - acl_access *ssl_bump_peeked; #endif #if FOLLOW_X_FORWARDED_FOR acl_access *followXFF; diff --git a/src/acl/AtBumpStep.cc b/src/acl/AtBumpStep.cc new file mode 100644 index 0000000000..0c85291b11 --- /dev/null +++ b/src/acl/AtBumpStep.cc @@ -0,0 +1,27 @@ +#include "squid.h" +#include "acl/Checklist.h" +#include "acl/AtBumpStep.h" +#include "acl/AtBumpStepData.h" +#include "client_side.h" +#include "ssl/ServerBump.h" +//#include "ssl/support.h" + +int +ACLAtStepStrategy::match (ACLData * &data, ACLFilledChecklist *checklist, ACLFlags &) +{ + if (checklist->conn() != NULL) { + if (Ssl::ServerBump *bump = checklist->conn()->serverBump()) + return data->match(bump->step); + else + return data->match(Ssl::bumpStep1); + } + return 0; +} + +ACLAtStepStrategy * +ACLAtStepStrategy::Instance() +{ + return &Instance_; +} + +ACLAtStepStrategy ACLAtStepStrategy::Instance_; diff --git a/src/acl/AtBumpStep.h b/src/acl/AtBumpStep.h new file mode 100644 index 0000000000..26385a6b28 --- /dev/null +++ b/src/acl/AtBumpStep.h @@ -0,0 +1,33 @@ +#ifndef SQUID_ACLATSTEP_H +#define SQUID_ACLATSTEP_H +#include "acl/Strategised.h" +#include "acl/Strategy.h" +#include "ssl/support.h" + +class ACLAtStepStrategy : public ACLStrategy +{ + +public: + virtual int match (ACLData * &, ACLFilledChecklist *, ACLFlags &); + static ACLAtStepStrategy *Instance(); + /* Not implemented to prevent copies of the instance. */ + /* Not private to prevent brain dead g+++ warnings about + * private constructors with no friends */ + ACLAtStepStrategy(ACLAtStepStrategy const &); + +private: + static ACLAtStepStrategy Instance_; + ACLAtStepStrategy() {} + + ACLAtStepStrategy&operator=(ACLAtStepStrategy const &); +}; + +class ACLAtStep +{ + +private: + static ACL::Prototype RegistryProtoype; + static ACLStrategised RegistryEntry_; +}; + +#endif /* SQUID_ACLATSTEP_H */ diff --git a/src/acl/AtBumpStepData.cc b/src/acl/AtBumpStepData.cc new file mode 100644 index 0000000000..4aadb3299f --- /dev/null +++ b/src/acl/AtBumpStepData.cc @@ -0,0 +1,69 @@ +#include "squid.h" +#include "acl/Checklist.h" +#include "acl/AtBumpStepData.h" +#include "cache_cf.h" +#include "Debug.h" +#include "wordlist.h" + +ACLAtStepData::ACLAtStepData() +{} + +ACLAtStepData::ACLAtStepData(ACLAtStepData const &old) +{ + values.assign(old.values.begin(), old.values.end()); +} + +ACLAtStepData::~ACLAtStepData() +{ +} + +bool +ACLAtStepData::match(Ssl::BumpStep toFind) +{ + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) { + if (*it == toFind) + return true; + } + return false; +} + +SBufList +ACLAtStepData::dump() const +{ + SBufList sl; + for (std::list::const_iterator it = values.begin(); it != values.end(); ++it) { + sl.push_back(SBuf(*it == Ssl::bumpStep1 ? "step1" : + *it == Ssl::bumpStep2 ? "step2" : + *it == Ssl::bumpStep3 ? "step3" : "???")); + } + return sl; +} + +void +ACLAtStepData::parse() +{ + while (const char *t = strtokFile()) { + if (strcasecmp(t, "step1") == 0) { + values.push_back(Ssl::bumpStep1); + } else if (strcasecmp(t, "step2") == 0) { + values.push_back(Ssl::bumpStep2); + } else if (strcasecmp(t, "step3") == 0) { + values.push_back(Ssl::bumpStep3); + } else { + debugs(28, DBG_CRITICAL, "FATAL: invalid AtStep step: " << t); + self_destruct(); + } + } +} + +bool +ACLAtStepData::empty() const +{ + return values.empty(); +} + +ACLAtStepData * +ACLAtStepData::clone() const +{ + return new ACLAtStepData(*this); +} diff --git a/src/acl/AtBumpStepData.h b/src/acl/AtBumpStepData.h new file mode 100644 index 0000000000..2effeb2a60 --- /dev/null +++ b/src/acl/AtBumpStepData.h @@ -0,0 +1,30 @@ +#ifndef SQUID_ACLATSTEPDATA_H +#define SQUID_ACLATSTEPDATA_H +#include "acl/Acl.h" +#include "acl/Data.h" +#include "CbDataList.h" +#include "ssl/support.h" +#include + +class ACLAtStepData : public ACLData +{ + +public: + MEMPROXY_CLASS(ACLAtStepData); + + ACLAtStepData(); + ACLAtStepData(ACLAtStepData const &); + ACLAtStepData &operator= (ACLAtStepData const &); + virtual ~ACLAtStepData(); + bool match(Ssl::BumpStep); + virtual SBufList dump() const; + void parse(); + bool empty() const; + virtual ACLAtStepData *clone() const; + + std::list values; +}; + +MEMPROXY_CLASS_INLINE(ACLAtStepData); + +#endif /* SQUID_ACLSSL_ERRORDATA_H */ diff --git a/src/acl/Makefile.am b/src/acl/Makefile.am index 03bb6cb7b5..e5051b35dc 100644 --- a/src/acl/Makefile.am +++ b/src/acl/Makefile.am @@ -31,6 +31,10 @@ libstate_la_SOURCES = \ ## data-specific ACLs libacls_la_SOURCES = \ + AtBumpStep.cc \ + AtBumpStep.h \ + AtBumpStepData.cc \ + AtBumpStepData.h \ IntRange.cc \ IntRange.h \ RegexData.cc \ diff --git a/src/cache_cf.cc b/src/cache_cf.cc index dd70b946db..3edc4ab3ac 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -243,9 +243,6 @@ static void parse_sslproxy_ssl_bump(acl_access **ssl_bump); static void dump_sslproxy_ssl_bump(StoreEntry *entry, const char *name, acl_access *ssl_bump); static void free_sslproxy_ssl_bump(acl_access **ssl_bump); -static void parse_sslproxy_ssl_bump_peeked(acl_access **ssl_bump); -static void dump_sslproxy_ssl_bump_peeked(StoreEntry *entry, const char *name, acl_access *ssl_bump); -static void free_sslproxy_ssl_bump_peeked(acl_access **ssl_bump); #endif /* USE_OPENSSL */ static void parse_ftp_epsv(acl_access **ftp_epsv); @@ -4672,8 +4669,23 @@ static void parse_sslproxy_ssl_bump(acl_access **ssl_bump) } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) { action.kind = Ssl::bumpServerFirst; bumpCfgStyleNow = bcsNew; - } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpPeekAndSplice]) == 0) { - action.kind = Ssl::bumpPeekAndSplice; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpPeek]) == 0) { + action.kind = Ssl::bumpPeek; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpStare]) == 0) { + action.kind = Ssl::bumpStare; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpSplice]) == 0) { + action.kind = Ssl::bumpSplice; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpBump]) == 0) { + action.kind = Ssl::bumpBump; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpTerminate]) == 0) { + action.kind = Ssl::bumpTerminate; + bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpErr]) == 0) { + action.kind = Ssl::bumpErr; bumpCfgStyleNow = bcsNew; } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpNone]) == 0) { action.kind = Ssl::bumpNone; @@ -4733,53 +4745,6 @@ static void free_sslproxy_ssl_bump(acl_access **ssl_bump) free_acl_access(ssl_bump); } -static void -parse_sslproxy_ssl_bump_peeked(acl_access **ssl_bump_peeked) -{ - char *bm; - if ((bm = ConfigParser::NextToken()) == NULL) { - self_destruct(); - return; - } - - allow_t action = allow_t(ACCESS_ALLOWED); - - if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) { - action.kind = Ssl::bumpServerFirst; - } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpSplice]) == 0) { - action.kind = Ssl::bumpSplice; - } else { - debugs(3, DBG_CRITICAL, "FATAL: unknown ssl_bump_peeked mode: " << bm); - self_destruct(); - return; - } - - Acl::AndNode *rule = new Acl::AndNode; - rule->context("(ssl_bump_peeked rule)", config_input_line); - rule->lineParse(); - - assert(ssl_bump_peeked); - if (!*ssl_bump_peeked) { - *ssl_bump_peeked = new Acl::Tree; - (*ssl_bump_peeked)->context("(ssl_bump_peeked rules)", config_input_line); - } - - (*ssl_bump_peeked)->add(rule, action); -} - -static void -dump_sslproxy_ssl_bump_peeked(StoreEntry *entry, const char *name, acl_access *ssl_bump_peeked) -{ - if (ssl_bump_peeked) - dump_SBufList(entry, ssl_bump_peeked->treeDump(name, Ssl::BumpModeStr)); -} - -static void -free_sslproxy_ssl_bump_peeked(acl_access **ssl_bump_peeked) -{ - free_acl_access(ssl_bump_peeked); -} - #endif static void dump_HeaderWithAclList(StoreEntry * entry, const char *name, HeaderWithAclList *headers) diff --git a/src/cf.data.pre b/src/cf.data.pre index 191c5fe319..f5306810ec 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -1148,6 +1148,12 @@ IF USE_OPENSSL # Optional argument specifies the digest algorithm to use. # The SHA1 digest algorithm is the default and is currently # the only algorithm supported (-sha1). + + acl aclname atstep step + # match against SSL bumping step. Valid SSL bumping step values: + # step1: Get TCP-level and CONNECT info. + # step2: Get SSL Client Hello info. + # step3: Get SSL Server Hello info. ENDIF acl aclname any-of acl1 acl2 ... # match any one of the acls [fast or slow] @@ -2445,6 +2451,30 @@ DOC_START ssl_bump [!]acl ... The following bumping modes are supported: + splice + Become a TCP tunnel without decoding the connection. + + bump + Establish a secure connection with the server and, using a + mimicked server certificate, with the client. + + peek + Receive client (step1) or server (step2) certificate while + preserving the possibility of splicing the connection. Peeking + at the server certificate usually precludes future bumping of + the connection. This action is the focus of this project. + + stare + Receive client (step1) or server (step2) certificate while + preserving the possibility of bumping the connection. Staring + at the server certificate usually precludes future splicing of + the connection. Currently, we are not aware of any work being + done to support this action. + + terminate + Close client and server connections. + + Compatibility modes: client-first Allow bumping of the connection. Establish a secure connection @@ -2491,29 +2521,6 @@ DOC_START ssl_bump server-first all DOC_END -NAME: ssl_bump_peeked -IFDEF: USE_OPENSSL -TYPE: sslproxy_ssl_bump_peeked -LOC: Config.accessList.ssl_bump_peeked -DEFAULT: none -DOC_START - This option used to control splicing decision after we peek at the - certificates - - ssl_bump_peeked [!]acl ... - - The following peeked modes are supported: - - server-first - Allow bumping the connection - - splice - Do not bump the connection if possible. - - ssl_bump_peeked server-first safeToBump - ssl_bump_peeked splice all -DOC_END - NAME: sslproxy_flags IFDEF: USE_OPENSSL DEFAULT: none diff --git a/src/client_side.cc b/src/client_side.cc index 4831e42b67..8630c4bdad 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -3621,7 +3621,7 @@ httpsSslBumpAccessCheckDone(allow_t answer, void *data) // Require both a match and a positive bump mode to work around exceptional // cases where ACL code may return ACCESS_ALLOWED with zero answer.kind. - if (answer == ACCESS_ALLOWED && answer.kind != Ssl::bumpNone) { + if (answer == ACCESS_ALLOWED && (answer.kind != Ssl::bumpNone && answer.kind != Ssl::bumpSplice)) { debugs(33, 2, HERE << "sslBump needed for " << connState->clientConnection); connState->sslBumpMode = static_cast(answer.kind); httpsEstablish(connState, NULL, (Ssl::BumpMode)answer.kind); @@ -3727,7 +3727,7 @@ ConnStateData::sslCrtdHandleReply(const HelperReply &reply) debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody()); } else { debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd"); - if (sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice) { + if (sslServerBump && (sslServerBump->mode == Ssl::bumpPeek || sslServerBump->mode == Ssl::bumpStare)) { doPeekAndSpliceStep(); SSL *ssl = fd_table[clientConnection->fd].ssl; bool ret = Ssl::configureSSLUsingPkeyAndCertFromMemory(ssl, reply_message.getBody().c_str(), *port); @@ -3844,7 +3844,7 @@ ConnStateData::getSslContextStart() assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0'); // Disable caching for bumpPeekAndSplice mode - if (!(sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice)) { + if (!(sslServerBump && (sslServerBump->mode == Ssl::bumpPeek || sslServerBump->mode == Ssl::bumpStare))) { debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache"); Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); SSL_CTX * dynCtx = NULL; @@ -3884,7 +3884,7 @@ ConnStateData::getSslContextStart() #endif // USE_SSL_CRTD debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName); - if (sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice) { + if (sslServerBump && (sslServerBump->mode == Ssl::bumpPeek || sslServerBump->mode == Ssl::bumpStare)) { doPeekAndSpliceStep(); SSL *ssl = fd_table[clientConnection->fd].ssl; if (!Ssl::configureSSL(ssl, certProperties, *port)) @@ -3976,9 +3976,9 @@ ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw()); return; } - else if (bumpServerMode == Ssl::bumpPeekAndSplice) { + else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) { request->flags.sslPeek = true; - sslServerBump = new Ssl::ServerBump(request, NULL, Ssl::bumpPeekAndSplice); + sslServerBump = new Ssl::ServerBump(request, NULL, bumpServerMode); startPeekAndSplice(); return; } @@ -4033,9 +4033,81 @@ void ConnStateData::startPeekAndSplice() bio->hold(true); } +int default_read_method(int, char *, int); +int default_write_method(int, const char *, int); +void httpsSslBumpStep2AccessCheckDone(allow_t answer, void *data) +{ + ConnStateData *connState = (ConnStateData *) data; + + // if the connection is closed or closing, just return. + if (!connState->isOpen()) + return; + + debugs(33, 5, HERE << "Answer: " << answer << " kind:" << answer.kind); + if (answer == ACCESS_ALLOWED && answer.kind != Ssl::bumpNone && answer.kind != Ssl::bumpSplice) { + if (answer.kind == Ssl::bumpTerminate || answer.kind == Ssl::bumpErr) + comm_close(connState->clientConnection->fd); + else { + if (answer.kind != Ssl::bumpPeek || answer.kind == Ssl::bumpStare) + connState->sslBumpMode = Ssl::bumpBump; + else + connState->sslBumpMode = (Ssl::BumpMode)answer.kind; + connState->startPeekAndSpliceDone(); + } + } else { + //Normally we can splice here, because we just got client hello message + SSL *ssl = fd_table[connState->clientConnection->fd].ssl; + BIO *b = SSL_get_rbio(ssl); + Ssl::ClientBio *bio = static_cast(b->ptr); + MemBuf const &rbuf = bio->rBufData(); + debugs(83,5, "Bio for " << connState->clientConnection->fd << " read " << rbuf.contentSize() << " helo bytes"); + // Do splice: + + connState->sslBumpMode = Ssl::bumpSplice; + + if (connState->transparent()) { +#if 0 && HANDLE_TRANSPARENT_PEEK_AND_SLPICE + // fake a CONNECT request to force connState to tunnel + static char ip[MAX_IPSTRLEN]; + connState->clientConnection->local.toUrl(ip, sizeof(ip)); + connState->in.buf.assign("CONNECT ").append(ip).append(" HTTP/1.1\r\nHost: ").append(ip).append("\r\n\r\n").append(rbuf.content(), rbuf.contentSize()); + bool ret = connState->handleReadData(&in.buf); + if (ret) + ret = connState->clientParseRequests(); + + if (!ret) { + debugs(33, 2, HERE << "Failed to start fake CONNECT request for ssl spliced connection: " << connState->clientConnection); + connState->clientConnection->close(); + } +#endif + } else { + // in.buf still has the "CONNECT ..." request data, reset it to SSL hello message + connState->in.buf.append(rbuf.content(), rbuf.contentSize()); + fd_table[connState->clientConnection->fd].read_method = &default_read_method; + fd_table[connState->clientConnection->fd].write_method = &default_write_method; + ClientSocketContext::Pointer context = connState->getCurrentContext(); + ClientHttpRequest *http = context->http; + tunnelStart(http, &http->out.size, &http->al->http.code, http->al); + } + } +} + void ConnStateData::startPeekAndSpliceDone() { + // This is the Step2 of the SSL bumping + assert(sslServerBump); + if (sslServerBump->step == Ssl::bumpStep1) { + sslServerBump->step = Ssl::bumpStep2; + // Run a accessList check to check if want to splice or continue bumping + + ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, sslServerBump->request.getRaw(), NULL); + //acl_checklist->src_addr = params.conn->remote; + //acl_checklist->my_addr = s->s; + acl_checklist->nonBlockingCheck(httpsSslBumpStep2AccessCheckDone, this); + return; + } + FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw()); } diff --git a/src/client_side_request.h b/src/client_side_request.h index ce5c2720f8..5315e94d22 100644 --- a/src/client_side_request.h +++ b/src/client_side_request.h @@ -150,7 +150,7 @@ public: /// returns raw sslBump mode value Ssl::BumpMode sslBumpNeed() const { return sslBumpNeed_; } /// returns true if and only if the request needs to be bumped - bool sslBumpNeeded() const { return sslBumpNeed_ == Ssl::bumpServerFirst || sslBumpNeed_ == Ssl::bumpClientFirst || sslBumpNeed_ == Ssl::bumpPeekAndSplice; } + bool sslBumpNeeded() const { return sslBumpNeed_ == Ssl::bumpServerFirst || sslBumpNeed_ == Ssl::bumpClientFirst || sslBumpNeed_ == Ssl::bumpBump || sslBumpNeed_ == Ssl::bumpPeek || sslBumpNeed_ == Ssl::bumpStare; } /// set the sslBumpNeeded state void sslBumpNeed(Ssl::BumpMode mode); void sslBumpStart(); diff --git a/src/ssl/PeerConnector.cc b/src/ssl/PeerConnector.cc index 8103198774..339d127a31 100644 --- a/src/ssl/PeerConnector.cc +++ b/src/ssl/PeerConnector.cc @@ -136,7 +136,7 @@ Ssl::PeerConnector::initializeSsl() if (peer->sslSession) SSL_set_session(ssl, peer->sslSession); - } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice) { + } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) { SSL *clientSsl = fd_table[request->clientConnectionManager->clientConnection->fd].ssl; BIO *b = SSL_get_rbio(clientSsl); Ssl::ClientBio *clnBio = static_cast(b->ptr); @@ -151,11 +151,13 @@ Ssl::PeerConnector::initializeSsl() if (features.compressMethod == 0) SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); #endif + // Should we allow it for all protocols? if (features.sslVersion >= 3) { b = SSL_get_rbio(ssl); Ssl::ServerBio *srvBio = static_cast(b->ptr); srvBio->setClientFeatures(features); srvBio->recordInput(true); + srvBio->mode(request->clientConnectionManager->sslBumpMode); } } } else { @@ -277,23 +279,41 @@ Ssl::PeerConnector::cbCheckForPeekAndSplice(allow_t answer, void *data) bool Ssl::PeerConnector::checkForPeekAndSplice(bool checkDone, Ssl::BumpMode peekMode) { + // Mark Step3 of bumping + if (request->clientConnectionManager.valid()) { + if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { + serverBump->step = Ssl::bumpStep3; + } + } + + if (!checkDone) { + ACLFilledChecklist *acl_checklist = new ACLFilledChecklist( + ::Config.accessList.ssl_bump, + request.getRaw(), NULL); + acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSplice, this); + return false; + } + SSL *ssl = fd_table[serverConn->fd].ssl; BIO *b = SSL_get_rbio(ssl); Ssl::ServerBio *srvBio = static_cast(b->ptr); debugs(83,5, "Will check for peek and splice on fd " << serverConn->fd); - bool splice; - if (!srvBio->canSplice()) - splice = false; - else if (!checkDone) { - ACLFilledChecklist *acl_checklist = new ACLFilledChecklist( - ::Config.accessList.ssl_bump_peeked, - request.getRaw(), NULL); - acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSplice, this); - return false; - } else - splice = (peekMode == Ssl::bumpSplice); - if (!splice) { + // bump, peek, stare, server-first,client-first are all mean bump the connection + if (peekMode < Ssl::bumpSplice) + peekMode = Ssl::bumpBump; + +#if 1 || SSL_BUMP_FORCE_PEEK_OR_SPLICE + if (peekMode == Ssl::bumpSplice && !srvBio->canSplice()) + peekMode = Ssl::bumpPeek; + else if (peekMode == Ssl::bumpBump && !srvBio->canBump()) + peekMode = Ssl::bumpSplice; +#endif + + if (peekMode == Ssl::bumpTerminate || peekMode == Ssl::bumpErr) { + comm_close(serverConn->fd); + comm_close(clientConn->fd); + } else if (peekMode != Ssl::bumpSplice) { //Allow write, proceed with the connection srvBio->holdWrite(false); srvBio->recordInput(false); @@ -306,6 +326,7 @@ Ssl::PeerConnector::checkForPeekAndSplice(bool checkDone, Ssl::BumpMode peekMode switchToTunnel(request.getRaw(), &status_code, clientConn, serverConn); return false; } + return false; } void @@ -455,7 +476,7 @@ Ssl::PeerConnector::handleNegotiateError(const int ret) return; case SSL_ERROR_WANT_WRITE: - if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice && srvBio->holdWrite()) { + if ((request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) && srvBio->holdWrite()) { debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd); checkForPeekAndSplice(false, Ssl::bumpNone); return; @@ -473,7 +494,7 @@ Ssl::PeerConnector::handleNegotiateError(const int ret) // If the checklist decision is do not splice a new error will // occure in the next SSL_connect call, and we will fail again. #if 1 - if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice && srvBio->holdWrite()) { + if ((request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) && srvBio->holdWrite()) { debugs(81, DBG_IMPORTANT, "fwdNegotiateSSL: Error but, hold write on SSL connection on FD " << fd); checkForPeekAndSplice(false, Ssl::bumpNone); return; diff --git a/src/ssl/ServerBump.cc b/src/ssl/ServerBump.cc index 3bcd338641..1485d0ffb0 100644 --- a/src/ssl/ServerBump.cc +++ b/src/ssl/ServerBump.cc @@ -17,7 +17,8 @@ CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerBump); Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e, Ssl::BumpMode md): request(fakeRequest), sslErrors(NULL), - mode(md) + mode(md), + step(bumpStep1) { debugs(33, 4, HERE << "will peek at " << request->GetHost() << ':' << request->port); const char *uri = urlCanonical(request.getRaw()); diff --git a/src/ssl/ServerBump.h b/src/ssl/ServerBump.h index 12c7185d29..cdc94a722b 100644 --- a/src/ssl/ServerBump.h +++ b/src/ssl/ServerBump.h @@ -29,6 +29,7 @@ public: Ssl::X509_Pointer serverCert; ///< HTTPS server certificate Ssl::CertErrors *sslErrors; ///< SSL [certificate validation] errors Ssl::BumpMode mode; ///< The SSL server bump mode + Ssl::BumpStep step; ///< The SSL server bumping step private: store_client *sc; ///< dummy client to prevent entry trimming diff --git a/src/ssl/bio.cc b/src/ssl/bio.cc index 80039d018f..58c58a7cd3 100644 --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@ -306,13 +306,14 @@ Ssl::ServerBio::read(char *buf, int size, BIO *table) } bool -adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) +adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features, bool force) { + bool fail = false; // If the client supports compression but our context does not support // we can not adjust. if (features.compressMethod && ssl->ctx->comp_methods == NULL) { debugs(83, 5, "Client Hello Data supports compression, but we do not!"); - return false; + fail = true; } //Check ciphers list @@ -335,14 +336,14 @@ adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) } if (!found) { debugs(83, 5, "Client Hello Data supports cipher '"<< cipher <<"' but we do not support it!"); - return false; + fail = true; } } #if !defined(SSL_TLSEXT_HB_ENABLED) if (features.doHeartBeats) { debugs(83, 5, "Client Hello Data supports HeartBeats but we do not support!"); - return false; + fail = true; } #endif @@ -387,12 +388,18 @@ adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) } if (!found) { debugs(83, 5, "Extension " << *it << " does not supported!"); - return false; + fail = true ; } } + if (fail && !force) + return false; - debugs(83, 5, "Hello Data are OK and can be mimicked!"); + if (fail) { + debugs(83, 5, "Hello Data are OK but can not be bumped any more!"); + } + + debugs(83, 5, "Hello Data will be mimicked!"); //Adjust ssl structure data. @@ -412,7 +419,7 @@ adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features) ssl->init_num = mainHelloSize; ssl->s3->wpend_ret = mainHelloSize; ssl->s3->wpend_tot = mainHelloSize; - return true; + return !fail; } int @@ -425,7 +432,7 @@ Ssl::ServerBio::write(const char *buf, int size, BIO *table) return -1; } - if (!helloBuild) { + if (!helloBuild && (bumpMode_ == Ssl::bumpPeek || bumpMode_ == Ssl::bumpStare)) { if (helloMsg.isNull()) helloMsg.init(1024, 16384); @@ -439,11 +446,17 @@ Ssl::ServerBio::write(const char *buf, int size, BIO *table) SSL *ssl = fd_table[fd_].ssl; if (featuresSet && ssl && ssl->s3) { - if (adjustSSL(ssl, clientFeatures)) { + if (bumpMode_ == Ssl::bumpPeek) { + if (adjustSSL(ssl, clientFeatures, true)) + allowBump = true; allowSplice = true; - helloMsg.append(clientFeatures.helloMessage.content(), clientFeatures.helloMessage.contentSize()); - debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted"); + } else { /*Ssl::bumpStare*/ + allowBump = true; + if (adjustSSL(ssl, clientFeatures, false)) + allowSplice = true; } + helloMsg.append(clientFeatures.helloMessage.content(), clientFeatures.helloMessage.contentSize()); + debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted"); } } // If we do not build any hello message, copy the current @@ -452,6 +465,7 @@ Ssl::ServerBio::write(const char *buf, int size, BIO *table) helloBuild = true; helloMsgSize = helloMsg.contentSize(); + //allowBump = true; if (allowSplice) { // Do not write yet..... diff --git a/src/ssl/bio.h b/src/ssl/bio.h index eb8f08cd85..b6d2fe9972 100644 --- a/src/ssl/bio.h +++ b/src/ssl/bio.h @@ -49,7 +49,7 @@ public: MemBuf helloMessage; }; explicit Bio(const int anFd); - ~Bio(); + virtual ~Bio(); /// Writes the given data to socket virtual int write(const char *buf, int size, BIO *table); @@ -73,8 +73,10 @@ public: /// Tells ssl connection to use BIO and monitor state via stateChanged() static void Link(SSL *ssl, BIO *bio); + const MemBuf &rBufData() {return rbuf;} protected: const int fd_; ///< the SSL socket we are reading and writing + MemBuf rbuf; ///< Used to buffer input data. }; /// BIO node to handle socket IO for squid client side @@ -106,7 +108,6 @@ private: Bio::sslFeatures features; bool holdRead_; ///< The read hold state of the bio. bool holdWrite_; ///< The write hold state of the bio. - MemBuf rbuf; ///< Used to buffer input data. int headerState; int headerBytes; }; @@ -114,7 +115,7 @@ private: /// BIO node to handle socket IO for squid server side class ServerBio: public Bio { public: - explicit ServerBio(const int anFd): Bio(anFd), featuresSet(false), helloMsgSize(0), helloBuild(false), allowSplice(false), holdWrite_(false), record_(false) {} + explicit ServerBio(const int anFd): Bio(anFd), featuresSet(false), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), record_(false) {} /// The ServerBio version of the Ssl::Bio::stateChanged method virtual void stateChanged(const SSL *ssl, int where, int ret); /// The ServerBio version of the Ssl::Bio::write method @@ -134,8 +135,9 @@ public: bool holdWrite() const {return holdWrite_;} void holdWrite(bool h) {holdWrite_ = h;} void recordInput(bool r) {record_ = r;} - const MemBuf &rBufData() {return rbuf;} bool canSplice() {return allowSplice;} + bool canBump() {return allowBump;} + void mode(Ssl::BumpMode m) {bumpMode_ = m;} private: /// A random number to use as "client random" in client hello message sslFeatures clientFeatures; @@ -144,9 +146,10 @@ private: int helloMsgSize; bool helloBuild; ///< True if the client hello message sent to the server bool allowSplice; + bool allowBump; bool holdWrite_; ///< The write hold state of the bio. bool record_; - MemBuf rbuf; ///< Used to buffer input data. + Ssl::BumpMode bumpMode_; }; inline diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 3954723536..7668c36a6b 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -65,8 +65,12 @@ const char *Ssl::BumpModeStr[] = { "none", "client-first", "server-first", - "peek-and-splice", + "peek", + "stare", + "bump", "splice", + "terminate", + "err", NULL }; diff --git a/src/ssl/support.h b/src/ssl/support.h index 45af1afa7f..33bb0d0090 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -158,7 +158,9 @@ GETX509ATTRIBUTE GetX509Fingerprint; \ingroup ServerProtocolSSLAPI * Supported ssl-bump modes */ -enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpPeekAndSplice, bumpSplice, bumpEnd}; +enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpPeek, bumpStare, bumpBump, bumpSplice, bumpTerminate, bumpErr, bumpEnd}; + +enum BumpStep {bumpStep1, bumpStep2, bumpStep3}; /** \ingroup ServerProtocolSSLAPI diff --git a/src/tunnel.cc b/src/tunnel.cc index d8a4d8f4e2..519d1f927e 100644 --- a/src/tunnel.cc +++ b/src/tunnel.cc @@ -56,6 +56,7 @@ #if USE_OPENSSL #include "ssl/bio.h" #include "ssl/PeerConnector.h" +#include "ssl/ServerBump.h" #endif #include "tools.h" #if USE_DELAY_POOLS @@ -119,6 +120,11 @@ public: /// Whether the client sent a CONNECT request to us. bool clientExpectsConnectResponse() const { +#if USE_OPENSSL + // We are bumping and we had already send "OK CONNECTED" + if (http.valid() && http->getConn() && http->getConn()->serverBump() && http->getConn()->serverBump()->step > Ssl::bumpStep1) + return false; +#endif return !(request != NULL && (request->flags.interceptTproxy || request->flags.intercepted)); } @@ -1153,13 +1159,26 @@ switchToTunnel(HttpRequest *request, int *status_ptr, Comm::ConnectionPointer &c fd_table[srvConn->fd].read_method = &default_read_method; fd_table[srvConn->fd].write_method = &default_write_method; - SSL *ssl = fd_table[srvConn->fd].ssl; - assert(ssl); - BIO *b = SSL_get_rbio(ssl); - Ssl::ServerBio *srvBio = static_cast(b->ptr); - const MemBuf &buf = srvBio->rBufData(); + ConnStateData *conn; + if ((conn = request->pinnedConnection()) && conn->serverBump() && conn->serverBump()->step == Ssl::bumpStep2) { + SSL *ssl = fd_table[clientConn->fd].ssl; + assert(ssl); + BIO *b = SSL_get_rbio(ssl); + Ssl::ClientBio *srvBio = static_cast(b->ptr); + const MemBuf &buf = srvBio->rBufData(); - AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", - CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); - Comm::Write(tunnelState->client.conn, buf.content(), buf.contentSize(), call, NULL); + AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", + CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); + Comm::Write(tunnelState->server.conn, buf.content(), buf.contentSize(), call, NULL); + } else { + SSL *ssl = fd_table[srvConn->fd].ssl; + assert(ssl); + BIO *b = SSL_get_rbio(ssl); + Ssl::ServerBio *srvBio = static_cast(b->ptr); + const MemBuf &buf = srvBio->rBufData(); + + AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", + CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); + Comm::Write(tunnelState->client.conn, buf.content(), buf.contentSize(), call, NULL); + } }