SSL Peek and Splice The goal of this patch is to make SSL bumping decision after the origin server name is known. Peek and Splice peeks at the SSL client Hello message and SNI info if any (bumping step 1), sends identical or a similar Hello message to the SSL server and peeks at the SSL server Hello message (bumping step 2), and finally decides to proceed with splicing or bumping the connection (bumping step 3). After the step 1 bumping step completes the SNI information is available and after the step 2 bumping step completes the server certificate is available. The ssl_bump access list evaluated on every bumping step to select the bumping mode to use. The new acl "at_step" can be used to match the current bumping step. In most cases: - if the user select "peek" bumping mode at step2 then at step3 can select one of the "splice" or "terminate" modes. - If the user select "stare" bumping mode at step2 then at step 3 can select one of the "bump" or "terminate" modes. If the squid built with the SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK and the client uses openSSL library similar to the library used by squid then bumping is possible after "peek" bumping mode selection and "splice" after "stare" bumping mode selection. The bump, terminate and splice are final decisions. Example configurations: acl step1 at_step SslBump1 acl step2 at_step SslBump2 acl step3 at_step SslBump3 ssl_bump peek step1 all ssl_bump splice step2 BANKS ssl_bump peek step2 all ssl_bump terminate step3 BLACKLIST ssl_bump splice step3 all This is a Measurement Factory project === modified file 'acinclude/lib-checks.m4' --- acinclude/lib-checks.m4 2013-06-30 15:54:22 +0000 +++ acinclude/lib-checks.m4 2014-08-11 10:35:26 +0000 @@ -260,20 +260,62 @@ static int index_serial_cmp(const char **a, const char **b){} static IMPLEMENT_LHASH_HASH_FN(index_serial_hash,const char **) static IMPLEMENT_LHASH_COMP_FN(index_serial_cmp,const char **) ], [ TXT_DB *db = NULL; TXT_DB_create_index(db, 1, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp)); ]) ], [ AC_MSG_RESULT([no]) ], [ AC_MSG_RESULT([yes]) AC_DEFINE(SQUID_USE_SSLLHASH_HACK, 1) ], []) SQUID_STATE_ROLLBACK(check_TXTDB) ]) + +dnl Check if we can rewrite the hello message stored in an SSL object. +dnl The tests are very basic, just check if the required members exist in +dnl SSL structure. +AC_DEFUN([SQUID_CHECK_OPENSSL_HELLO_OVERWRITE_HACK],[ + AH_TEMPLATE(SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK, "Define to 1 if hello message can be overwritten in SSL struct") + SQUID_STATE_SAVE(check_openSSL_overwrite_hack) + AC_MSG_CHECKING(whether hello message can be overwritten in SSL struct) + + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM( + [ + #include + #include + #include + ], + [ + SSL *ssl; + char *random, *msg; + memcpy(ssl->s3->client_random, random, SSL3_RANDOM_SIZE); + SSL3_BUFFER *wb=&(ssl->s3->wbuf); + assert(wb->len == 0); + memcpy(wb->buf, msg, 0); + assert(wb->left == 0); + memcpy(ssl->init_buf->data, msg, 0); + ssl->init_num = 0; + ssl->s3->wpend_ret = 0; + ssl->s3->wpend_tot = 0; + ]) + ], + [ + AC_DEFINE(SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK, 1) + AC_MSG_RESULT([yes]) + ], + [ + AC_MSG_RESULT([no]) + ], + []) + +SQUID_STATE_ROLLBACK(check_openSSL_overwrite_hack) +] +) === modified file 'configure.ac' --- configure.ac 2014-07-13 08:49:42 +0000 +++ configure.ac 2014-08-25 09:08:15 +0000 @@ -1270,40 +1270,41 @@ the OpenSSL development libraries and headers installation can be specified if outside of the system standard directories]), [ case "$with_openssl" in yes|no) : # Nothing special to do here ;; *) if test ! -d "$withval" ; then AC_MSG_ERROR([--with-openssl path does not point to a directory]) fi LIBOPENSSL_PATH="-L$with_openssl/lib" CPPFLAGS="-I$with_openssl/include $CPPFLAGS" with_openssl=yes esac ]) AH_TEMPLATE(USE_OPENSSL,[OpenSSL support is available]) ## OpenSSL is default disable due to licensing issues on some OS if test "x$with_openssl" = "xyes"; then AC_CHECK_HEADERS( \ + openssl/bio.h \ openssl/err.h \ openssl/md5.h \ openssl/opensslv.h \ openssl/ssl.h \ openssl/x509v3.h \ openssl/engine.h \ openssl/txt_db.h \ ) # User may have provided a custom location for OpenSSL. Otherwise... SQUID_STATE_SAVE(squid_openssl_state) LIBS="$LIBS $LIBOPENSSL_PATH" # auto-detect using pkg-config PKG_CHECK_MODULES([LIBOPENSSL],[openssl],,[ ## For some OS pkg-config is broken or unavailable. ## Detect libraries the hard way. # Windows MinGW has some special libraries ... if test "x$squid_host_os" = "xmingw" ; then @@ -1319,40 +1320,41 @@ ]) ]) # This is a workaround for RedHat 9 brain damage.. if test -d /usr/kerberos/include -a -f /usr/include/openssl/kssl.h; then AC_MSG_NOTICE([OpenSSL depends on Kerberos]) LIBOPENSSL_LIBS="-L/usr/kerberos/lib $LIBOPENSSL_LIBS" CPPFLAGS="$CPPFLAGS -I/usr/kerberos/include" fi SQUID_STATE_ROLLBACK(squid_openssl_state) #de-pollute LIBS if test "x$LIBOPENSSL_LIBS" != "x"; then CXXFLAGS="$LIBOPENSSL_CFLAGS $CXXFLAGS" SSLLIB="$LIBOPENSSL_PATH $LIBOPENSSL_LIBS $SSLLIB" AC_DEFINE(USE_OPENSSL,1,[OpenSSL support is available]) # check for other specific broken implementations SQUID_CHECK_OPENSSL_GETCERTIFICATE_WORKS SQUID_CHECK_OPENSSL_CONST_SSL_METHOD SQUID_CHECK_OPENSSL_TXTDB + SQUID_CHECK_OPENSSL_HELLO_OVERWRITE_HACK fi if test "x$SSLLIB" = "x"; then AC_MSG_ERROR([Required OpenSSL library not found]) fi fi AC_MSG_NOTICE([OpenSSL library support: ${with_openssl:=no} ${LIBOPENSSL_PATH} ${LIBOPENSSL_LIBS}]) AM_CONDITIONAL(ENABLE_SSL,[ test "x$with_openssl" = "xyes" ]) AC_SUBST(SSLLIB) AC_ARG_ENABLE(forw-via-db, AS_HELP_STRING([--enable-forw-via-db],[Enable Forw/Via database]), [ SQUID_YESNO([$enableval],[unrecognized argument to --enable-forw-via-db: $enableval]) ]) SQUID_DEFINE_BOOL(USE_FORW_VIA_DB,${enable_forw_via_db:=no}, [Enable Forw/Via database]) AC_MSG_NOTICE([Forw/Via database enabled: $enable_forw_via_db]) AC_ARG_ENABLE(cache-digests, AS_HELP_STRING([--enable-cache-digests], [Use Cache Digests. See http://wiki.squid-cache.org/SquidFaq/CacheDigests]), === modified file 'src/AclRegs.cc' --- src/AclRegs.cc 2014-03-30 12:00:34 +0000 +++ src/AclRegs.cc 2014-08-25 09:31:43 +0000 @@ -1,37 +1,41 @@ #include "squid.h" /** This file exists to provide satic registration code to executables that need ACLs. We cannot place this code in acl/lib*.la because it does not get linked in, because nobody is using these classes by name. */ #if USE_ADAPTATION #include "acl/AdaptationService.h" #include "acl/AdaptationServiceData.h" #endif #include "acl/AllOf.h" #include "acl/AnyOf.h" #if USE_SQUID_EUI #include "acl/Arp.h" #include "acl/Eui64.h" #endif +#if USE_OPENSSL +#include "acl/AtStep.h" +#include "acl/AtStepData.h" +#endif #include "acl/Asn.h" #include "acl/Browser.h" #include "acl/Checklist.h" #include "acl/Data.h" #include "acl/DestinationAsn.h" #include "acl/DestinationDomain.h" #include "acl/DestinationIp.h" #include "acl/DomainData.h" #if USE_AUTH #include "acl/ExtUser.h" #endif #include "acl/FilledChecklist.h" #include "acl/Gadgets.h" #include "acl/HierCode.h" #include "acl/HierCodeData.h" #include "acl/HttpHeaderData.h" #include "acl/HttpRepHeader.h" #include "acl/HttpReqHeader.h" #include "acl/HttpStatus.h" #include "acl/IntRange.h" @@ -143,40 +147,43 @@ ACLStrategised ACLTime::RegistryEntry_(new ACLTimeData, ACLTimeStrategy::Instance(), "time"); ACL::Prototype ACLUrl::RegistryProtoype(&ACLUrl::RegistryEntry_, "url_regex"); ACLStrategised ACLUrl::RegistryEntry_(new ACLRegexData, ACLUrlStrategy::Instance(), "url_regex"); ACL::Prototype ACLUrlLogin::RegistryProtoype(&ACLUrlLogin::RegistryEntry_, "urllogin"); ACLStrategised ACLUrlLogin::RegistryEntry_(new ACLRegexData, ACLUrlLoginStrategy::Instance(), "urllogin"); ACL::Prototype ACLUrlPath::LegacyRegistryProtoype(&ACLUrlPath::RegistryEntry_, "pattern"); ACL::Prototype ACLUrlPath::RegistryProtoype(&ACLUrlPath::RegistryEntry_, "urlpath_regex"); ACLStrategised ACLUrlPath::RegistryEntry_(new ACLRegexData, ACLUrlPathStrategy::Instance(), "urlpath_regex"); ACL::Prototype ACLUrlPort::RegistryProtoype(&ACLUrlPort::RegistryEntry_, "port"); ACLStrategised ACLUrlPort::RegistryEntry_(new ACLIntRange, ACLUrlPortStrategy::Instance(), "port"); #if USE_OPENSSL ACL::Prototype ACLSslError::RegistryProtoype(&ACLSslError::RegistryEntry_, "ssl_error"); ACLStrategised ACLSslError::RegistryEntry_(new ACLSslErrorData, ACLSslErrorStrategy::Instance(), "ssl_error"); ACL::Prototype ACLCertificate::UserRegistryProtoype(&ACLCertificate::UserRegistryEntry_, "user_cert"); ACLStrategised ACLCertificate::UserRegistryEntry_(new ACLCertificateData (Ssl::GetX509UserAttribute, "*"), ACLCertificateStrategy::Instance(), "user_cert"); ACL::Prototype ACLCertificate::CARegistryProtoype(&ACLCertificate::CARegistryEntry_, "ca_cert"); 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_, "at_step"); +ACLStrategised ACLAtStep::RegistryEntry_(new ACLAtStepData, ACLAtStepStrategy::Instance(), "at_step"); #endif #if USE_SQUID_EUI ACL::Prototype ACLARP::RegistryProtoype(&ACLARP::RegistryEntry_, "arp"); ACLARP ACLARP::RegistryEntry_("arp"); ACL::Prototype ACLEui64::RegistryProtoype(&ACLEui64::RegistryEntry_, "eui64"); ACLEui64 ACLEui64::RegistryEntry_("eui64"); #endif #if USE_IDENT ACL::Prototype ACLIdent::UserRegistryProtoype(&ACLIdent::UserRegistryEntry_, "ident"); ACLIdent ACLIdent::UserRegistryEntry_(new ACLUserData, "ident"); ACL::Prototype ACLIdent::RegexRegistryProtoype(&ACLIdent::RegexRegistryEntry_, "ident_regex" ); ACLIdent ACLIdent::RegexRegistryEntry_(new ACLRegexData, "ident_regex"); #endif #if USE_AUTH ACL::Prototype ACLProxyAuth::UserRegistryProtoype(&ACLProxyAuth::UserRegistryEntry_, "proxy_auth"); ACLProxyAuth ACLProxyAuth::UserRegistryEntry_(new ACLUserData, "proxy_auth"); ACL::Prototype ACLProxyAuth::RegexRegistryProtoype(&ACLProxyAuth::RegexRegistryEntry_, "proxy_auth_regex" ); === modified file 'src/FwdState.cc' --- src/FwdState.cc 2014-07-21 14:55:27 +0000 +++ src/FwdState.cc 2014-08-11 16:47:46 +0000 @@ -695,41 +695,41 @@ debugs(17, 3, HERE << serverConnection() << ": '" << entry->url() << "'" ); comm_add_close_handler(serverConnection()->fd, fwdServerClosedWrapper, this); if (serverConnection()->getPeer()) peerConnectSucceded(serverConnection()->getPeer()); #if USE_OPENSSL if (!request->flags.pinned) { if ((serverConnection()->getPeer() && serverConnection()->getPeer()->use_ssl) || (!serverConnection()->getPeer() && request->url.getScheme() == AnyP::PROTO_HTTPS) || request->flags.sslPeek) { HttpRequest::Pointer requestPointer = request; AsyncCall::Pointer callback = asyncCall(17,4, "FwdState::ConnectedToPeer", FwdStatePeerAnswerDialer(&FwdState::connectedToPeer, this)); // Use positive timeout when less than one second is left. const time_t sslNegotiationTimeout = max(static_cast(1), timeLeft()); Ssl::PeerConnector *connector = - new Ssl::PeerConnector(requestPointer, serverConnection(), callback, sslNegotiationTimeout); + new Ssl::PeerConnector(requestPointer, serverConnection(), clientConn, callback, sslNegotiationTimeout); AsyncJob::Start(connector); // will call our callback return; } } #endif dispatch(); } #if USE_OPENSSL void FwdState::connectedToPeer(Ssl::PeerConnectorAnswer &answer) { if (ErrorState *error = answer.error.get()) { fail(error); answer.error.clear(); // preserve error for errorSendComplete() self = NULL; return; } === modified file 'src/PeerPoolMgr.cc' --- src/PeerPoolMgr.cc 2014-07-11 00:15:30 +0000 +++ src/PeerPoolMgr.cc 2014-08-13 09:55:28 +0000 @@ -103,41 +103,41 @@ Must(params.conn != NULL); #if USE_OPENSSL // Handle SSL peers. if (peer->use_ssl) { typedef CommCbMemFunT CloserDialer; closer = JobCallback(48, 3, CloserDialer, this, PeerPoolMgr::handleSecureClosure); comm_add_close_handler(params.conn->fd, closer); securer = asyncCall(48, 4, "PeerPoolMgr::handleSecuredPeer", MyAnswerDialer(this, &PeerPoolMgr::handleSecuredPeer)); const int peerTimeout = peer->connect_timeout > 0 ? peer->connect_timeout : Config.Timeout.peer_connect; const int timeUsed = squid_curtime - params.conn->startTime(); // Use positive timeout when less than one second is left for conn. const int timeLeft = max(1, (peerTimeout - timeUsed)); Ssl::PeerConnector *connector = - new Ssl::PeerConnector(request, params.conn, securer, timeLeft); + new Ssl::PeerConnector(request, params.conn, NULL, securer, timeLeft); AsyncJob::Start(connector); // will call our callback return; } #endif pushNewConnection(params.conn); } void PeerPoolMgr::pushNewConnection(const Comm::ConnectionPointer &conn) { Must(validPeer()); Must(Comm::IsConnOpen(conn)); peer->standby.pool->push(conn, NULL /* domain */); // push() will trigger a checkpoint() } #if USE_OPENSSL void PeerPoolMgr::handleSecuredPeer(Ssl::PeerConnectorAnswer &answer) === added file 'src/acl/AtStep.cc' --- src/acl/AtStep.cc 1970-01-01 00:00:00 +0000 +++ src/acl/AtStep.cc 2014-08-25 09:26:50 +0000 @@ -0,0 +1,30 @@ +#include "squid.h" + +#if USE_OPENSSL + +#include "acl/Checklist.h" +#include "acl/AtStep.h" +#include "acl/AtStepData.h" +#include "client_side.h" +#include "ssl/ServerBump.h" + +int +ACLAtStepStrategy::match (ACLData * &data, ACLFilledChecklist *checklist, ACLFlags &) +{ + Ssl::ServerBump *bump = NULL; + if (checklist->conn() != NULL && (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_; + +#endif /* USE_OPENSSL */ === added file 'src/acl/AtStep.h' --- src/acl/AtStep.h 1970-01-01 00:00:00 +0000 +++ src/acl/AtStep.h 2014-08-25 09:36:58 +0000 @@ -0,0 +1,38 @@ +#ifndef SQUID_ACLATSTEP_H +#define SQUID_ACLATSTEP_H + +#if USE_OPENSSL + +#include "acl/Strategised.h" +#include "acl/Strategy.h" +#include "ssl/support.h" + +/// \ingroup ACLAPI +class ACLAtStepStrategy : public ACLStrategy +{ + +public: + virtual int match (ACLData * &, ACLFilledChecklist *, ACLFlags &); + static ACLAtStepStrategy *Instance(); + + // Not implemented to prevent copies of the instance. + ACLAtStepStrategy(ACLAtStepStrategy const &); + +private: + static ACLAtStepStrategy Instance_; + ACLAtStepStrategy() {} + + ACLAtStepStrategy&operator=(ACLAtStepStrategy const &); +}; + +class ACLAtStep +{ + +private: + static ACL::Prototype RegistryProtoype; + static ACLStrategised RegistryEntry_; +}; + +#endif /* USE_OPENSSL */ + +#endif /* SQUID_ACLATSTEP_H */ === added file 'src/acl/AtStepData.cc' --- src/acl/AtStepData.cc 1970-01-01 00:00:00 +0000 +++ src/acl/AtStepData.cc 2014-08-25 09:26:35 +0000 @@ -0,0 +1,74 @@ +#include "squid.h" + +#if USE_OPENSSL + +#include "acl/Checklist.h" +#include "acl/AtStepData.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 ? "SslBump1" : + *it == Ssl::bumpStep2 ? "SslBump2" : + *it == Ssl::bumpStep3 ? "SslBump3" : "???")); + } + return sl; +} + +void +ACLAtStepData::parse() +{ + while (const char *t = strtokFile()) { + if (strcasecmp(t, "SslBump1") == 0) { + values.push_back(Ssl::bumpStep1); + } else if (strcasecmp(t, "SslBump2") == 0) { + values.push_back(Ssl::bumpStep2); + } else if (strcasecmp(t, "SslBump3") == 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); +} + +#endif /* USE_OPENSSL */ === added file 'src/acl/AtStepData.h' --- src/acl/AtStepData.h 1970-01-01 00:00:00 +0000 +++ src/acl/AtStepData.h 2014-08-25 09:27:46 +0000 @@ -0,0 +1,36 @@ +#ifndef SQUID_ACLATSTEPDATA_H +#define SQUID_ACLATSTEPDATA_H + +#if USE_OPENSSL + +#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 /* USE_OPENSSL */ + +#endif /* SQUID_ACLSSL_ERRORDATA_H */ === modified file 'src/acl/Makefile.am' --- src/acl/Makefile.am 2013-11-12 14:48:50 +0000 +++ src/acl/Makefile.am 2014-08-25 09:24:19 +0000 @@ -116,40 +116,44 @@ UrlLogin.cc \ UrlLogin.h \ UrlPath.cc \ UrlPath.h \ UrlPort.cc \ UrlPort.h \ UserData.cc \ UserData.h \ AclNameList.h \ AclDenyInfoList.h \ Gadgets.cc \ Gadgets.h \ AclSizeLimit.h ## Add conditional sources ## TODO: move these to their respectful dirs when those dirs are created EXTRA_libacls_la_SOURCES = SSL_ACLS = \ + AtStep.cc \ + AtStep.h \ + AtStepData.cc \ + AtStepData.h \ CertificateData.cc \ CertificateData.h \ Certificate.cc \ Certificate.h \ ServerCertificate.cc \ ServerCertificate.h \ SslError.cc \ SslError.h \ SslErrorData.cc \ SslErrorData.h if ENABLE_SSL libacls_la_SOURCES += $(SSL_ACLS) endif if USE_ADAPTATION libacls_la_SOURCES += AdaptationService.h \ AdaptationService.cc \ AdaptationServiceData.h \ AdaptationServiceData.cc === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2014-07-21 14:55:27 +0000 +++ src/cache_cf.cc 2014-08-11 16:47:46 +0000 @@ -4626,40 +4626,55 @@ char *bm; if ((bm = ConfigParser::NextToken()) == NULL) { self_destruct(); return; } // if this is the first rule proccessed if (*ssl_bump == NULL) { bumpCfgStyleLast = bcsNone; sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpEnd; } allow_t action = allow_t(ACCESS_ALLOWED); if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpClientFirst]) == 0) { action.kind = Ssl::bumpClientFirst; bumpCfgStyleNow = bcsNew; } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) { action.kind = Ssl::bumpServerFirst; bumpCfgStyleNow = bcsNew; + } 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::bumpNone]) == 0) { action.kind = Ssl::bumpNone; bumpCfgStyleNow = bcsNew; } else if (strcmp(bm, "allow") == 0) { debugs(3, DBG_CRITICAL, "SECURITY NOTICE: auto-converting deprecated " "\"ssl_bump allow \" to \"ssl_bump client-first \" which " "is usually inferior to the newer server-first " "bumping mode. Update your ssl_bump rules."); action.kind = Ssl::bumpClientFirst; bumpCfgStyleNow = bcsOld; sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpClientFirst; } else if (strcmp(bm, "deny") == 0) { debugs(3, DBG_CRITICAL, "WARNING: auto-converting deprecated " "\"ssl_bump deny \" to \"ssl_bump none \". Update " "your ssl_bump rules."); action.kind = Ssl::bumpNone; bumpCfgStyleNow = bcsOld; sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpNone; } else { debugs(3, DBG_CRITICAL, "FATAL: unknown ssl_bump mode: " << bm); === modified file 'src/cf.data.depend' --- src/cf.data.depend 2014-04-04 16:36:47 +0000 +++ src/cf.data.depend 2014-05-06 08:07:41 +0000 @@ -56,23 +56,24 @@ PortCfg QosConfig TokenOrQuotedString refreshpattern removalpolicy size_t IpAddress_list string string time_msec time_t tristate uri_whitespace u_short wccp2_method wccp2_amethod wccp2_service wccp2_service_info wordlist sslproxy_ssl_bump acl +sslproxy_ssl_bump_peeked acl sslproxy_cert_sign acl sslproxy_cert_adapt acl ftp_epsv acl === modified file 'src/cf.data.pre' --- src/cf.data.pre 2014-08-02 14:03:21 +0000 +++ src/cf.data.pre 2014-08-11 10:52:38 +0000 @@ -659,40 +659,43 @@ %LOGIN Authenticated user login name %EXT_USER Username from previous external acl %EXT_LOG Log details from previous external acl %EXT_TAG Tag from previous external acl %IDENT Ident user name %SRC Client IP %SRCPORT Client source port %URI Requested URI %DST Requested host %PROTO Requested URL scheme %PORT Requested port %PATH Requested URL path %METHOD Request method %MYADDR Squid interface address %MYPORT Squid http_port number %PATH Requested URL-path (including query-string if any) %USER_CERT SSL User certificate in PEM format %USER_CERTCHAIN SSL User certificate chain in PEM format %USER_CERT_xx SSL User certificate subject attribute xx %USER_CA_CERT_xx SSL User certificate issuer attribute xx + %ssl::>sni SSL client SNI sent to Squid + %ssl::{Header} HTTP request header "Header" %>{Hdr:member} HTTP request header "Hdr" list member "member" %>{Hdr:;member} HTTP request header list member using ; as list separator. ; can be any non-alphanumeric character. %<{Header} HTTP reply header "Header" %<{Hdr:member} HTTP reply header "Hdr" list member "member" %<{Hdr:;member} HTTP reply header list member using ; as list separator. ; can be any non-alphanumeric character. %ACL The name of the ACL being tested. %DATA The ACL arguments. If not used then any arguments is automatically added at the end of the line @@ -1073,40 +1076,51 @@ # [ssl::]certUntrusted: The certificate issuer is not to be trusted. # [ssl::]certSelfSigned: The certificate is self signed. # [ssl::]certDomainMismatch: The certificate CN domain does not # match the name the name of the host we are connecting to. # # The ssl::certHasExpired, ssl::certNotYetValid, ssl::certDomainMismatch, # ssl::certUntrusted, and ssl::certSelfSigned can also be used as # predefined ACLs, just like the 'all' ACL. # # NOTE: The ssl_error ACL is only supported with sslproxy_cert_error, # sslproxy_cert_sign, and sslproxy_cert_adapt options. acl aclname server_cert_fingerprint [-sha1] fingerprint # match against server SSL certificate fingerprint [fast] # # The fingerprint is the digest of the DER encoded version # of the whole certificate. The user should use the form: XX:XX:... # 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 at_step step + # match against the current step during ssl_bump evaluation [fast] + # Never matches and should not be used outside the ssl_bump context. + # + # At each SslBump step, Squid evaluates ssl_bump directives to find + # the next bumping action (e.g., peek or splice). Valid SslBump step + # values and the corresponding ssl_bump evaluation moments are: + # SslBump1: After getting TCP-level and HTTP CONNECT info. + # SslBump2: After getting SSL Client Hello info. + # SslBump3: After getting SSL Server Hello info. ENDIF acl aclname any-of acl1 acl2 ... # match any one of the acls [fast or slow] # The first matching ACL stops further ACL evaluation. # # ACLs from multiple any-of lines with the same name are ORed. # For example, A = (a1 or a2) or (a3 or a4) can be written as # acl A any-of a1 a2 # acl A any-of a3 a4 # # This group ACL is fast if all evaluated ACLs in the group are fast # and slow otherwise. acl aclname all-of acl1 acl2 ... # match all of the acls [fast or slow] # The first mismatching ACL stops further ACL evaluation. # # ACLs from multiple all-of lines with the same name are ORed. # For example, B = (b1 and b2) or (b3 and b4) can be written as # acl B all-of b1 b2 @@ -2361,93 +2375,119 @@ DEFAULT: 300 LOC: Config.SSL.session_ttl TYPE: int DOC_START Sets the timeout value for SSL sessions DOC_END NAME: sslproxy_session_cache_size IFDEF: USE_OPENSSL DEFAULT: 2 MB LOC: Config.SSL.sessionCacheSize TYPE: b_size_t DOC_START Sets the cache size to use for ssl session DOC_END NAME: ssl_bump IFDEF: USE_OPENSSL TYPE: sslproxy_ssl_bump LOC: Config.accessList.ssl_bump -DEFAULT_DOC: Does not bump unless rules are present in squid.conf +DEFAULT_DOC: Become a TCP tunnel without decrypting proxied traffic. DEFAULT: none DOC_START This option is consulted when a CONNECT request is received on an http_port (or a new connection is intercepted at an https_port), provided that port was configured with an ssl-bump flag. The subsequent data on the connection is either treated as HTTPS and decrypted OR tunneled at TCP level without decryption, - depending on the first bumping "mode" which ACLs match. + depending on the first matching bumping "action". + + ssl_bump [!]acl ... + + The following bumping actions are currently supported: + + splice + Become a TCP tunnel without decrypting proxied traffic. + This is the default action. - ssl_bump [!]acl ... + bump + Establish a secure connection with the server and, using a + mimicked server certificate, with the client. - The following bumping modes are supported: + peek + Receive client (step SslBump1) or server (step SslBump2) + certificate while preserving the possibility of splicing the + connection. Peeking at the server certificate (during step 2) + usually precludes bumping of the connection at step 3. + + stare + Receive client (step SslBump1) or server (step SslBump2) + certificate while preserving the possibility of bumping the + connection. Staring at the server certificate (during step 2) + usually precludes splicing of the connection at step 3. + + terminate + Close client and server connections. + + Backward compatibility actions available at step SslBump1: client-first - Allow bumping of the connection. Establish a secure connection - with the client first, then connect to the server. This old mode - does not allow Squid to mimic server SSL certificate and does - not work with intercepted SSL connections. + Bump the connection. Establish a secure connection with the + client first, then connect to the server. This old mode does + not allow Squid to mimic server SSL certificate and does not + work with intercepted SSL connections. server-first - Allow bumping of the connection. Establish a secure connection - with the server first, then establish a secure connection with - the client, using a mimicked server certificate. Works with both - CONNECT requests and intercepted SSL connections. + Bump the connection. Establish a secure connection with the + server first, then establish a secure connection with the + client, using a mimicked server certificate. Works with both + CONNECT requests and intercepted SSL connections, but does + not allow to make decisions based on SSL handshake info. + + peek-and-splice + Decide whether to bump or splice the connection based on + client-to-squid and server-to-squid SSL hello messages. + XXX: Remove. none - Become a TCP tunnel without decoding the connection. - Works with both CONNECT requests and intercepted SSL - connections. This is the default behavior when no - ssl_bump option is given or no ssl_bump ACLs match. - - By default, no connections are bumped. - - The first matching ssl_bump option wins. If no ACLs match, the - connection is not bumped. Unlike most allow/deny ACL lists, ssl_bump - does not have an implicit "negate the last given option" rule. You - must make that rule explicit if you convert old ssl_bump allow/deny - rules that rely on such an implicit rule. + Same as the "splice" action. + + All ssl_bump rules are evaluated at each of the supported bumping + steps. Rules with actions that are impossible at the current step are + ignored. The first matching ssl_bump action wins and is applied at the + end of the current step. If no rules match, the splice action is used. + See the at_step ACL for a list of the supported SslBump steps. This clause supports both fast and slow acl types. See http://wiki.squid-cache.org/SquidFaq/SquidAcl for details. - See also: http_port ssl-bump, https_port ssl-bump + See also: http_port ssl-bump, https_port ssl-bump, and acl at_step. # Example: Bump all requests except those originating from # localhost or those going to example.com. acl broken_sites dstdomain .example.com - ssl_bump none localhost - ssl_bump none broken_sites - ssl_bump server-first all + ssl_bump splice localhost + ssl_bump splice broken_sites + ssl_bump bump all DOC_END NAME: sslproxy_flags IFDEF: USE_OPENSSL DEFAULT: none LOC: Config.ssl_client.flags TYPE: string DOC_START Various flags modifying the use of SSL while proxying https:// URLs: DONT_VERIFY_PEER Accept certificates that fail verification. For refined control, see sslproxy_cert_error. NO_DEFAULT_CA Don't use the default CA list built in to OpenSSL. DOC_END NAME: sslproxy_cert_error IFDEF: USE_OPENSSL DEFAULT: none DEFAULT_DOC: Server certificate errors terminate the transaction. LOC: Config.ssl_client.cert_error @@ -3882,40 +3922,44 @@ Sh Squid hierarchy status (DEFAULT_PARENT etc) SSL-related format codes: ssl::bump_mode SslBump decision for the transaction: For CONNECT requests that initiated bumping of a connection and for any request received on an already bumped connection, Squid logs the corresponding SslBump mode ("server-first" or "client-first"). See the ssl_bump option for more information about these modes. A "none" token is logged for requests that triggered "ssl_bump" ACL evaluation matching either a "none" rule or no rules at all. In all other cases, a single dash ("-") is logged. + ssl::>sni SSL client SNI sent to Squid. Available only + after the peek, stare, or splice SSL bumping + actions. + If ICAP is enabled, the following code becomes available (as well as ICAP log codes documented with the icap_log option): icap::tt Total ICAP processing time for the HTTP transaction. The timer ticks when ICAP ACLs are checked and when ICAP transaction is in progress. If adaptation is enabled the following three codes become available: adapt:: #include #include #if LINGERING_CLOSE #define comm_close comm_lingering_close #endif @@ -3389,117 +3390,117 @@ /* setup write limiter for this request */ const double burst = floor(0.5 + (pools[pool].highwatermark * Config.ClientDelay.initial)/100.0); cli->setWriteLimiter(pools[pool].rate, burst, pools[pool].highwatermark); break; } else { debugs(83, 4, HERE << "Delay pool " << pool << " skipped because ACL " << answer); } } } } #endif } #if USE_OPENSSL /** Create SSL connection structure and update fd_table */ static SSL * httpsCreate(const Comm::ConnectionPointer &conn, SSL_CTX *sslContext) { - SSL *ssl = SSL_new(sslContext); - - if (!ssl) { - const int ssl_error = ERR_get_error(); - debugs(83, DBG_IMPORTANT, "ERROR: httpsAccept: Error allocating handle: " << ERR_error_string(ssl_error, NULL) ); - conn->close(); - return NULL; + if (SSL *ssl = Ssl::CreateServer(sslContext, conn->fd, "client https start")) { + debugs(33, 5, "will negotate SSL on " << conn); + return ssl; } - SSL_set_fd(ssl, conn->fd); - fd_table[conn->fd].ssl = ssl; - fd_table[conn->fd].read_method = &ssl_read_method; - fd_table[conn->fd].write_method = &ssl_write_method; - - debugs(33, 5, "httpsCreate: will negotate SSL on " << conn); - fd_note(conn->fd, "client https start"); - - return ssl; + conn->close(); + return NULL; } -/** negotiate an SSL connection */ -static void -clientNegotiateSSL(int fd, void *data) +static bool +Squid_SSL_accept(ConnStateData *conn, PF *callback) { - ConnStateData *conn = (ConnStateData *)data; - X509 *client_cert; + int fd = conn->clientConnection->fd; SSL *ssl = fd_table[fd].ssl; int ret; if ((ret = SSL_accept(ssl)) <= 0) { int ssl_error = SSL_get_error(ssl, ret); switch (ssl_error) { case SSL_ERROR_WANT_READ: - Comm::SetSelect(fd, COMM_SELECT_READ, clientNegotiateSSL, conn, 0); - return; + Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0); + return false; case SSL_ERROR_WANT_WRITE: - Comm::SetSelect(fd, COMM_SELECT_WRITE, clientNegotiateSSL, conn, 0); - return; + Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0); + return false; case SSL_ERROR_SYSCALL: if (ret == 0) { - debugs(83, 2, "clientNegotiateSSL: Error negotiating SSL connection on FD " << fd << ": Aborted by client"); + debugs(83, 2, "Error negotiating SSL connection on FD " << fd << ": Aborted by client: " << ssl_error); comm_close(fd); - return; + return false; } else { int hard = 1; if (errno == ECONNRESET) hard = 0; - debugs(83, hard ? 1 : 2, "clientNegotiateSSL: Error negotiating SSL connection on FD " << + debugs(83, hard ? 1 : 2, "Error negotiating SSL connection on FD " << fd << ": " << strerror(errno) << " (" << errno << ")"); comm_close(fd); - return; + return false; } case SSL_ERROR_ZERO_RETURN: - debugs(83, DBG_IMPORTANT, "clientNegotiateSSL: Error negotiating SSL connection on FD " << fd << ": Closed by client"); + debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": Closed by client"); comm_close(fd); - return; + return false; default: - debugs(83, DBG_IMPORTANT, "clientNegotiateSSL: Error negotiating SSL connection on FD " << + debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": " << ERR_error_string(ERR_get_error(), NULL) << " (" << ssl_error << "/" << ret << ")"); comm_close(fd); - return; + return false; } /* NOTREACHED */ } + return true; +} + +/** negotiate an SSL connection */ +static void +clientNegotiateSSL(int fd, void *data) +{ + ConnStateData *conn = (ConnStateData *)data; + X509 *client_cert; + SSL *ssl = fd_table[fd].ssl; + + if (!Squid_SSL_accept(conn, clientNegotiateSSL)) + return; if (SSL_session_reused(ssl)) { debugs(83, 2, "clientNegotiateSSL: Session " << SSL_get_session(ssl) << " reused on FD " << fd << " (" << fd_table[fd].ipaddr << ":" << (int)fd_table[fd].remote_port << ")"); } else { if (do_debug(83, 4)) { /* Write out the SSL session details.. actually the call below, but * OpenSSL headers do strange typecasts confusing GCC.. */ /* PEM_write_SSL_SESSION(debug_log, SSL_get_session(ssl)); */ #if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x00908000L PEM_ASN1_write((i2d_of_void *)i2d_SSL_SESSION, PEM_STRING_SSL_SESSION, debug_log, (char *)SSL_get_session(ssl), NULL,NULL,0,NULL,NULL); #elif (ALLOW_ALWAYS_SSL_SESSION_DETAIL == 1) /* When using gcc 3.3.x and OpenSSL 0.9.7x sometimes a compile error can occur here. * This is caused by an unpredicatble gcc behaviour on a cast of the first argument * of PEM_ASN1_write(). For this reason this code section is disabled. To enable it, * define ALLOW_ALWAYS_SSL_SESSION_DETAIL=1. * Because there are two possible usable cast, if you get an error here, try the other * commented line. */ @@ -3592,42 +3593,42 @@ connState->switchToHttps(fakeRequest.getRaw(), bumpMode); } } /** * A callback function to use with the ACLFilledChecklist callback. * In the case of ACCESS_ALLOWED answer initializes a bumped SSL connection, * else reverts the connection to tunnel mode. */ static void httpsSslBumpAccessCheckDone(allow_t answer, void *data) { ConnStateData *connState = (ConnStateData *) data; // if the connection is closed or closing, just return. if (!connState->isOpen()) return; // 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) { - debugs(33, 2, HERE << "sslBump needed for " << connState->clientConnection); + if (answer == ACCESS_ALLOWED && (answer.kind != Ssl::bumpNone && answer.kind != Ssl::bumpSplice)) { + debugs(33, 2, "sslBump needed for " << connState->clientConnection << " method " << answer.kind); connState->sslBumpMode = static_cast(answer.kind); httpsEstablish(connState, NULL, (Ssl::BumpMode)answer.kind); } else { debugs(33, 2, HERE << "sslBump not needed for " << connState->clientConnection); connState->sslBumpMode = Ssl::bumpNone; // fake a CONNECT request to force connState to tunnel static char ip[MAX_IPSTRLEN]; connState->clientConnection->local.toUrl(ip, sizeof(ip)); // Pre-pend this fake request to the TLS bits already in the buffer SBuf retStr; retStr.append("CONNECT ").append(ip).append(" HTTP/1.1\r\nHost: ").append(ip).append("\r\n\r\n"); connState->in.buf = retStr.append(connState->in.buf); bool ret = connState->handleReadData(); if (ret) ret = connState->clientParseRequests(); if (!ret) { debugs(33, 2, HERE << "Failed to start fake CONNECT request for ssl bumped connection: " << connState->clientConnection); connState->clientConnection->close(); @@ -3663,75 +3664,84 @@ ConnStateData *connState = new ConnStateData(xact); if (s->flags.tunnelSslBumping) { debugs(33, 5, "httpsAccept: accept transparent connection: " << params.conn); if (!Config.accessList.ssl_bump) { httpsSslBumpAccessCheckDone(ACCESS_DENIED, connState); return; } // Create a fake HTTP request for ssl_bump ACL check, // using tproxy/intercept provided destination IP and port. HttpRequest *request = new HttpRequest(); static char ip[MAX_IPSTRLEN]; assert(params.conn->flags & (COMM_TRANSPARENT | COMM_INTERCEPTION)); request->SetHost(params.conn->local.toStr(ip, sizeof(ip))); request->port = params.conn->local.port(); request->myportname = s->name; ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL); + acl_checklist->conn(connState); acl_checklist->src_addr = params.conn->remote; acl_checklist->my_addr = s->s; acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, connState); return; } else { SSL_CTX *sslContext = s->staticSslContext.get(); httpsEstablish(connState, sslContext, Ssl::bumpNone); } } void ConnStateData::sslCrtdHandleReplyWrapper(void *data, const HelperReply &reply) { ConnStateData * state_data = (ConnStateData *)(data); state_data->sslCrtdHandleReply(reply); } void ConnStateData::sslCrtdHandleReply(const HelperReply &reply) { if (reply.result == HelperReply::BrokenHelper) { debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply); } else if (!reply.other().hasContent()) { debugs(1, DBG_IMPORTANT, HERE << "\"ssl_crtd\" helper returned reply."); } else { Ssl::CrtdMessage reply_message(Ssl::CrtdMessage::REPLY); if (reply_message.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK) { debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslConnectHostOrIp << " is incorrect"); } else { if (reply.result != HelperReply::Okay) { 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"); - SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port); - getSslContextDone(ctx, true); + 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); + if (!ret) + debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode"); + } else { + SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port); + getSslContextDone(ctx, true); + } return; } } } getSslContextDone(NULL); } void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties) { certProperties.commonName = sslCommonName.size() > 0 ? sslCommonName.termedBuf() : sslConnectHostOrIp.termedBuf(); // fake certificate adaptation requires bump-server-first mode if (!sslServerBump) { assert(port->signingCert.get()); certProperties.signWithX509.resetAndLock(port->signingCert.get()); if (port->signPkey.get()) certProperties.signWithPkey.resetAndLock(port->signPkey.get()); certProperties.signAlgorithm = Ssl::algSignTrusted; return; } @@ -3804,80 +3814,90 @@ if (port->signPkey.get()) certProperties.signWithPkey.resetAndLock(port->signPkey.get()); } signAlgorithm = certProperties.signAlgorithm; } void ConnStateData::getSslContextStart() { assert(areAllContextsForThisConnection()); freeAllContexts(); /* careful: freeAllContexts() above frees request, host, etc. */ if (port->generateHostCertificates) { Ssl::CertificateProperties certProperties; buildSslCertGenerationParams(certProperties); sslBumpCertKey = certProperties.dbKey().c_str(); assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0'); - 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; - Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL; - if (cachedCtx && (dynCtx = cachedCtx->get())) { - debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache"); - if (Ssl::verifySslCertificate(dynCtx, certProperties)) { - debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid"); - getSslContextDone(dynCtx); - return; + // Disable caching for bumpPeekAndSplice mode + if (!(sslServerBump && (sslServerBump->mode == Ssl::bumpPeek || sslServerBump->mode == Ssl::bumpStare))) { + debugs(33, 5, "Finding SSL certificate for " << sslBumpCertKey << " in cache"); + Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); + SSL_CTX * dynCtx = NULL; + Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : NULL; + if (cachedCtx && (dynCtx = cachedCtx->get())) { + debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " found in cache"); + if (Ssl::verifySslCertificate(dynCtx, certProperties)) { + debugs(33, 5, "Cached SSL certificate for " << sslBumpCertKey << " is valid"); + getSslContextDone(dynCtx); + return; + } else { + debugs(33, 5, "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); + if (ssl_ctx_cache) + ssl_ctx_cache->del(sslBumpCertKey.termedBuf()); + } } else { - debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); - if (ssl_ctx_cache) - ssl_ctx_cache->del(sslBumpCertKey.termedBuf()); + debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); } - } else { - debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); } #if USE_SSL_CRTD try { debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName << " using ssl_crtd."); Ssl::CrtdMessage request_message(Ssl::CrtdMessage::REQUEST); request_message.setCode(Ssl::CrtdMessage::code_new_certificate); request_message.composeRequest(certProperties); debugs(33, 5, HERE << "SSL crtd request: " << request_message.compose().c_str()); Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); return; } catch (const std::exception &e) { debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtd " << "request for " << certProperties.commonName << " certificate: " << e.what() << "; will now block to " << "generate that certificate."); // fall through to do blocking in-process generation. } #endif // USE_SSL_CRTD debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName); - dynCtx = Ssl::generateSslContext(certProperties, *port); - getSslContextDone(dynCtx, true); + if (sslServerBump && (sslServerBump->mode == Ssl::bumpPeek || sslServerBump->mode == Ssl::bumpStare)) { + doPeekAndSpliceStep(); + SSL *ssl = fd_table[clientConnection->fd].ssl; + if (!Ssl::configureSSL(ssl, certProperties, *port)) + debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode"); + } else { + SSL_CTX *dynCtx = Ssl::generateSslContext(certProperties, *port); + getSslContextDone(dynCtx, true); + } return; } getSslContextDone(NULL); } void ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) { // Try to add generated ssl context to storage. if (port->generateHostCertificates && isNew) { if (signAlgorithm == Ssl::algSignTrusted) { // Add signing certificate to the certificates chain X509 *cert = port->signingCert.get(); if (SSL_CTX_add_extra_chain_cert(sslContext, cert)) { // increase the certificate lock CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); } else { const int ssl_error = ERR_get_error(); debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL)); @@ -3932,46 +3952,192 @@ { assert(!switchedToHttps_); sslConnectHostOrIp = request->GetHost(); sslCommonName = request->GetHost(); // We are going to read new request flags.readMore = true; debugs(33, 5, HERE << "converting " << clientConnection << " to SSL"); // If sslServerBump is set, then we have decided to deny CONNECT // and now want to switch to SSL to send the error to the client // without even peeking at the origin server certificate. if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) { request->flags.sslPeek = true; sslServerBump = new Ssl::ServerBump(request); // will call httpsPeeked() with certificate and connection, eventually FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw()); return; + } else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) { + request->flags.sslPeek = true; + sslServerBump = new Ssl::ServerBump(request, NULL, bumpServerMode); + startPeekAndSplice(); + return; } // otherwise, use sslConnectHostOrIp getSslContextStart(); } +/** negotiate an SSL connection */ +static void +clientPeekAndSpliceSSL(int fd, void *data) +{ + ConnStateData *conn = (ConnStateData *)data; + SSL *ssl = fd_table[fd].ssl; + + debugs(83, 5, "Start peek and splice on FD " << fd); + + if (!Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) + debugs(83, 2, "SSL_accept failed."); + + BIO *b = SSL_get_rbio(ssl); + assert(b); + Ssl::ClientBio *bio = static_cast(b->ptr); + if (bio->gotHello()) { + if (conn->serverBump()) { + Ssl::Bio::sslFeatures const &features = bio->getFeatures(); + if (!features.serverName.isEmpty()) + conn->serverBump()->clientSni = features.serverName; + } + + 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 + SSL_CTX *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. + + // Disable the client read handler until CachePeer selection is complete + Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); + Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0); + switchedToHttps_ = true; + + SSL *ssl = fd_table[clientConnection->fd].ssl; + BIO *b = SSL_get_rbio(ssl); + Ssl::ClientBio *bio = static_cast(b->ptr); + bio->hold(true); +} + +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, "Answer: " << answer << " kind:" << answer.kind); + if (answer == ACCESS_ALLOWED && answer.kind != Ssl::bumpNone && answer.kind != Ssl::bumpSplice) { + if (answer.kind == Ssl::bumpTerminate) + 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 << " read " << rbuf.contentSize() << " helo bytes"); + // Do splice: + + connState->sslBumpMode = Ssl::bumpSplice; + fd_table[connState->clientConnection->fd].read_method = &default_read_method; + fd_table[connState->clientConnection->fd].write_method = &default_write_method; + + if (connState->transparent()) { + // 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(); + if (ret) + ret = connState->clientParseRequests(); + + if (!ret) { + debugs(33, 2, "Failed to start fake CONNECT request for ssl spliced connection: " << connState->clientConnection); + connState->clientConnection->close(); + } + } else { + // in.buf still has the "CONNECT ..." request data, reset it to SSL hello message + connState->in.buf.append(rbuf.content(), rbuf.contentSize()); + 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()); +} + +void +ConnStateData::doPeekAndSpliceStep() +{ + SSL *ssl = fd_table[clientConnection->fd].ssl; + BIO *b = SSL_get_rbio(ssl); + assert(b); + Ssl::ClientBio *bio = static_cast(b->ptr); + + debugs(33, 5, "PeekAndSplice mode, proceed with client negotiation. Currrent state:" << SSL_state_string_long(ssl)); + bio->hold(false); + + Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, this, 0); + switchedToHttps_ = true; +} + void ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) { Must(sslServerBump != NULL); if (Comm::IsConnOpen(serverConnection)) { SSL *ssl = fd_table[serverConnection->fd].ssl; assert(ssl); Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl)); assert(serverCert.get() != NULL); sslCommonName = Ssl::CommonHostName(serverCert.get()); debugs(33, 5, HERE << "HTTPS server CN: " << sslCommonName << " bumped: " << *serverConnection); pinConnection(serverConnection, NULL, NULL, false); debugs(33, 5, HERE << "bumped HTTPS server: " << sslConnectHostOrIp); } else { debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp); Ip::Address intendedDest; === modified file 'src/client_side.h' --- src/client_side.h 2014-07-14 09:48:47 +0000 +++ src/client_side.h 2014-08-11 16:47:46 +0000 @@ -324,40 +324,48 @@ // pining related comm callbacks void clientPinnedConnectionClosed(const CommCloseCbParams &io); // comm callbacks void clientReadRequest(const CommIoCbParams &io); void connStateClosed(const CommCloseCbParams &io); void requestTimeout(const CommTimeoutCbParams ¶ms); // AsyncJob API virtual bool doneAll() const { return BodyProducer::doneAll() && false;} virtual void swanSong(); /// Changes state so that we close the connection and quit after serving /// the client-side-detected error response instead of getting stuck. void quitAfterError(HttpRequest *request); // meant to be private /// The caller assumes responsibility for connection closure detection. void stopPinnedConnectionMonitoring(); #if USE_OPENSSL + /// Initializes and starts a peek-and-splice negotiation with the SSL client + void startPeekAndSplice(); + /// Called when the initialization of peek-and-splice negotiation finidhed + void startPeekAndSpliceDone(); + /// Called when a peek-and-splice step finished. For example after + /// server-side SSL certificates received and client-side SSL certificates + /// generated + void doPeekAndSpliceStep(); /// called by FwdState when it is done bumping the server void httpsPeeked(Comm::ConnectionPointer serverConnection); /// Start to create dynamic SSL_CTX for host or uses static port SSL context. void getSslContextStart(); /** * Done create dynamic ssl certificate. * * \param[in] isNew if generated certificate is new, so we need to add this certificate to storage. */ void getSslContextDone(SSL_CTX * sslContext, bool isNew = false); /// Callback function. It is called when squid receive message from ssl_crtd. static void sslCrtdHandleReplyWrapper(void *data, const HelperReply &reply); /// Proccess response from ssl_crtd. void sslCrtdHandleReply(const HelperReply &reply); void switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode); bool switchedToHttps() const { return switchedToHttps_; } Ssl::ServerBump *serverBump() {return sslServerBump;} inline void setServerBump(Ssl::ServerBump *srvBump) { === modified file 'src/client_side_request.h' --- src/client_side_request.h 2014-06-05 14:57:58 +0000 +++ src/client_side_request.h 2014-08-13 09:56:26 +0000 @@ -133,41 +133,41 @@ virtual bool doneAll() const { return Initiator::doneAll() && BodyConsumer::doneAll() && false; } #endif private: int64_t maxReplyBodySize_; StoreEntry *entry_; StoreEntry *loggingEntry_; ConnStateData * conn_; #if USE_OPENSSL /// whether (and how) the request needs to be bumped Ssl::BumpMode sslBumpNeed_; 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; } + 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(); void sslBumpEstablish(Comm::Flag errflag); #endif #if USE_ADAPTATION public: void startAdaptation(const Adaptation::ServiceGroupPointer &g); // private but exposed for ClientRequestContext void handleAdaptationFailure(int errDetail, bool bypassable = false); private: // Adaptation::Initiator API virtual void noteAdaptationAnswer(const Adaptation::Answer &answer); void handleAdaptedHeader(HttpMsg *msg); void handleAdaptationBlock(const Adaptation::Answer &answer); virtual void noteAdaptationAclCheckDone(Adaptation::ServiceGroupPointer group); === modified file 'src/external_acl.cc' --- src/external_acl.cc 2014-07-30 15:31:10 +0000 +++ src/external_acl.cc 2014-08-11 16:47:46 +0000 @@ -48,40 +48,41 @@ #include "ExternalACL.h" #include "ExternalACLEntry.h" #include "fde.h" #include "format/ByteCode.h" #include "helper.h" #include "HttpHeaderTools.h" #include "HttpReply.h" #include "HttpRequest.h" #include "ip/tools.h" #include "MemBuf.h" #include "mgr/Registration.h" #include "rfc1738.h" #include "SquidConfig.h" #include "SquidString.h" #include "SquidTime.h" #include "Store.h" #include "tools.h" #include "URL.h" #include "wordlist.h" #if USE_OPENSSL +#include "ssl/ServerBump.h" #include "ssl/support.h" #endif #if USE_AUTH #include "auth/Acl.h" #include "auth/Gadgets.h" #include "auth/UserRequest.h" #endif #if USE_IDENT #include "ident/AclIdent.h" #endif #ifndef DEFAULT_EXTERNAL_ACL_TTL #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60 #endif #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN #define DEFAULT_EXTERNAL_ACL_CHILDREN 5 #endif typedef struct _external_acl_format external_acl_format; @@ -406,41 +407,46 @@ format->type = Format::LFT_CLIENT_REQ_URLPORT; else if (strcmp(token, "%PATH") == 0 || strcmp(token, "%>rp") == 0) format->type = Format::LFT_CLIENT_REQ_URLPATH; else if (strcmp(token, "%METHOD") == 0 || strcmp(token, "%>rm") == 0) format->type = Format::LFT_CLIENT_REQ_METHOD; #if USE_OPENSSL else if (strcmp(token, "%USER_CERT") == 0) format->type = Format::LFT_EXT_ACL_USER_CERT_RAW; else if (strcmp(token, "%USER_CERTCHAIN") == 0) format->type = Format::LFT_EXT_ACL_USER_CERTCHAIN_RAW; else if (strncmp(token, "%USER_CERT_", 11) == 0) { format->type = Format::LFT_EXT_ACL_USER_CERT; format->header = xstrdup(token + 11); } else if (strncmp(token, "%USER_CA_CERT_", 14) == 0) { format->type = Format::LFT_EXT_ACL_USER_CA_CERT; format->header = xstrdup(token + 14); } else if (strncmp(token, "%CA_CERT_", 9) == 0) { debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type %CA_CERT_* code is obsolete. Use %USER_CA_CERT_* instead"); format->type = Format::LFT_EXT_ACL_USER_CA_CERT; format->header = xstrdup(token + 9); - } + } else if (strcmp(token, "%ssl::>sni") == 0) + format->type = Format::LFT_SSL_CLIENT_SNI; + else if (strcmp(token, "%ssl::type = Format::LFT_SSL_SERVER_CERT_SUBJECT; + else if (strcmp(token, "%ssl::type = Format::LFT_SSL_SERVER_CERT_ISSUER; #endif #if USE_AUTH else if (strcmp(token, "%EXT_USER") == 0 || strcmp(token, "%ue") == 0) format->type = Format::LFT_USER_EXTERNAL; #endif else if (strcmp(token, "%EXT_LOG") == 0 || strcmp(token, "%ea") == 0) format->type = Format::LFT_EXT_LOG; else if (strcmp(token, "%TAG") == 0 || strcmp(token, "%et") == 0) format->type = Format::LFT_TAG; else if (strcmp(token, "%ACL") == 0) format->type = Format::LFT_EXT_ACL_NAME; else if (strcmp(token, "%DATA") == 0) format->type = Format::LFT_EXT_ACL_DATA; else if (strcmp(token, "%%") == 0) format->type = Format::LFT_PERCENT; else { debugs(0, DBG_CRITICAL, "ERROR: Unknown Format token " << token); self_destruct(); } @@ -542,40 +548,43 @@ #endif DUMP_EXT_ACL_TYPE_FMT(CLIENT_IP_ADDRESS," %%>a"); DUMP_EXT_ACL_TYPE_FMT(CLIENT_PORT," %%>p"); #if USE_SQUID_EUI DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI48," %%SRCEUI48"); DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI64," %%SRCEUI64"); #endif DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_IP," %%>la"); DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_PORT," %%>lp"); DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URI," %%>ru"); DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLDOMAIN," %%>rd"); DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLSCHEME," %%>rs"); DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPORT," %%>rP"); DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPATH," %%>rp"); DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_METHOD," %%>rm"); #if USE_OPENSSL DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT_RAW, " %%USER_CERT_RAW"); DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERTCHAIN_RAW, " %%USER_CERTCHAIN_RAW"); DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT, " %%USER_CERT_%s", format->header); DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CA_CERT, " %%USER_CA_CERT_%s", format->header); + DUMP_EXT_ACL_TYPE_FMT(SSL_CLIENT_SNI, "%%ssl::>sni"); + DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_SUBJECT, "%%ssl::cmdline; word; word = word->next) storeAppendPrintf(sentry, " %s", word->key); storeAppendPrintf(sentry, "\n"); } @@ -1061,40 +1070,67 @@ if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) { SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl; if (ssl) str = sslGetUserAttribute(ssl, format->header); } break; case Format::LFT_EXT_ACL_USER_CA_CERT: if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) { SSL *ssl = fd_table[ch->conn()->clientConnection->fd].ssl; if (ssl) str = sslGetCAAttribute(ssl, format->header); } break; + + case Format::LFT_SSL_CLIENT_SNI: + if (ch->conn() != NULL) { + if (Ssl::ServerBump * srvBump = ch->conn()->serverBump()) { + if (!srvBump->clientSni.isEmpty()) + str = srvBump->clientSni.c_str(); + } + } + break; + + case Format::LFT_SSL_SERVER_CERT_SUBJECT: + case Format::LFT_SSL_SERVER_CERT_ISSUER: { + X509 *serverCert = NULL; + if (ch->serverCert.get()) + serverCert = ch->serverCert.get(); + else if (ch->conn()->serverBump()) + serverCert = ch->conn()->serverBump()->serverCert.get(); + + if (serverCert) { + if (format->type == Format::LFT_SSL_SERVER_CERT_SUBJECT) + str = Ssl::GetX509UserAttribute(serverCert, "DN"); + else + str = Ssl::GetX509CAAttribute(serverCert, "DN"); + } + break; + } + #endif #if USE_AUTH case Format::LFT_USER_EXTERNAL: str = request->extacl_user.termedBuf(); break; #endif case Format::LFT_EXT_LOG: str = request->extacl_log.termedBuf(); break; case Format::LFT_TAG: str = request->tag.termedBuf(); break; case Format::LFT_EXT_ACL_NAME: str = acl_data->name; break; case Format::LFT_EXT_ACL_DATA: data_used = true; for (arg = acl_data->arguments; arg; arg = arg->next) { if (!first) sb.append(" ", 1); === modified file 'src/fd.h' --- src/fd.h 2012-09-20 11:28:21 +0000 +++ src/fd.h 2014-08-25 15:27:53 +0000 @@ -23,22 +23,24 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #ifndef SQUID_FD_H_ #define SQUID_FD_H_ void fd_close(int fd); void fd_open(int fd, unsigned int type, const char *); void fd_note(int fd, const char *); void fd_bytes(int fd, int len, unsigned int type); void fdDumpOpen(void); int fdUsageHigh(void); void fdAdjustReserved(void); +int default_read_method(int, char *, int); +int default_write_method(int, const char *, int); #endif /* SQUID_FD_H_ */ === modified file 'src/format/ByteCode.h' --- src/format/ByteCode.h 2014-05-08 10:17:41 +0000 +++ src/format/ByteCode.h 2014-08-11 16:47:46 +0000 @@ -189,40 +189,43 @@ LFT_ICAP_REQ_HEADER, LFT_ICAP_REQ_HEADER_ELEM, LFT_ICAP_REQ_ALL_HEADERS, LFT_ICAP_REP_HEADER, LFT_ICAP_REP_HEADER_ELEM, LFT_ICAP_REP_ALL_HEADERS, LFT_ICAP_TR_RESPONSE_TIME, LFT_ICAP_IO_TIME, LFT_ICAP_OUTCOME, LFT_ICAP_STATUS_CODE, #endif LFT_CREDENTIALS, #if USE_OPENSSL LFT_SSL_BUMP_MODE, LFT_SSL_USER_CERT_SUBJECT, LFT_SSL_USER_CERT_ISSUER, + LFT_SSL_CLIENT_SNI, + LFT_SSL_SERVER_CERT_SUBJECT, + LFT_SSL_SERVER_CERT_ISSUER, #endif LFT_NOTE, LFT_PERCENT, /* special string cases for escaped chars */ // TODO assign better bytecode names and Token strings for these LFT_EXT_ACL_USER_CERT_RAW, LFT_EXT_ACL_USER_CERTCHAIN_RAW, LFT_EXT_ACL_USER_CERT, LFT_EXT_ACL_USER_CA_CERT, LFT_EXT_ACL_CLIENT_EUI48, LFT_EXT_ACL_CLIENT_EUI64, LFT_EXT_ACL_NAME, LFT_EXT_ACL_DATA } ByteCode_t; /// Quoting style for a format output. enum Quoting { LOG_QUOTE_NONE = 0, === modified file 'src/format/Format.cc' --- src/format/Format.cc 2014-07-14 09:48:47 +0000 +++ src/format/Format.cc 2014-08-11 16:47:47 +0000 @@ -1,39 +1,40 @@ #include "squid.h" #include "AccessLogEntry.h" #include "client_side.h" #include "comm/Connection.h" #include "err_detail_type.h" #include "errorpage.h" #include "fde.h" #include "format/Format.h" #include "format/Quoting.h" #include "format/Token.h" #include "fqdncache.h" #include "HttpRequest.h" #include "MemBuf.h" #include "rfc1738.h" #include "SquidTime.h" #include "Store.h" #include "URL.h" #if USE_OPENSSL #include "ssl/ErrorDetail.h" +#include "ssl/ServerBump.h" #endif /// Convert a string to NULL pointer if it is "" #define strOrNull(s) ((s)==NULL||(s)[0]=='\0'?NULL:(s)) Format::Format::Format(const char *n) : format(NULL), next(NULL) { name = xstrdup(n); } Format::Format::~Format() { // erase the list without consuming stack space while (next) { // unlink the next entry for deletion Format *temp = next; next = temp->next; temp->next = NULL; @@ -1117,40 +1118,53 @@ break; } case LFT_SSL_USER_CERT_SUBJECT: if (X509 *cert = al->cache.sslClientCert.get()) { if (X509_NAME *subject = X509_get_subject_name(cert)) { X509_NAME_oneline(subject, tmp, sizeof(tmp)); out = tmp; } } break; case LFT_SSL_USER_CERT_ISSUER: if (X509 *cert = al->cache.sslClientCert.get()) { if (X509_NAME *issuer = X509_get_issuer_name(cert)) { X509_NAME_oneline(issuer, tmp, sizeof(tmp)); out = tmp; } } break; + case LFT_SSL_CLIENT_SNI: + if (al->request && al->request->clientConnectionManager.valid()) { + if (Ssl::ServerBump * srvBump = al->request->clientConnectionManager->serverBump()) { + if (!srvBump->clientSni.isEmpty()) + out = srvBump->clientSni.c_str(); + } + } + break; + + case LFT_SSL_SERVER_CERT_ISSUER: + case LFT_SSL_SERVER_CERT_SUBJECT: + // Not implemented + break; #endif case LFT_REQUEST_URLGROUP_OLD_2X: assert(LFT_REQUEST_URLGROUP_OLD_2X == 0); // should never happen. case LFT_NOTE: tmp[0] = fmt->data.header.separator; tmp[1] = '\0'; if (fmt->data.header.header && *fmt->data.header.header) { const char *separator = tmp; #if USE_ADAPTATION Adaptation::History::Pointer ah = al->request ? al->request->adaptHistory() : Adaptation::History::Pointer(); if (ah != NULL && ah->metaHeaders != NULL) { if (const char *meta = ah->metaHeaders->find(fmt->data.header.header, separator)) sb.append(meta); } #endif if (al->notes != NULL) { if (const char *note = al->notes->find(fmt->data.header.header, separator)) { if (sb.size()) === modified file 'src/format/Token.cc' --- src/format/Token.cc 2014-05-08 10:17:41 +0000 +++ src/format/Token.cc 2014-08-25 10:13:46 +0000 @@ -164,40 +164,43 @@ {"h", LFT_ICAP_REQ_HEADER}, {"cert_subject", LFT_SSL_USER_CERT_SUBJECT}, {">cert_issuer", LFT_SSL_USER_CERT_ISSUER}, + {">sni", LFT_SSL_CLIENT_SNI}, + /*{"(callback->getDialer())); } Ssl::PeerConnector::~PeerConnector() { debugs(83, 5, "Peer connector " << this << " gone"); } bool Ssl::PeerConnector::doneAll() const { return (!callback || callback->canceled()) && AsyncJob::doneAll(); } /// Preps connection and SSL state. Calls negotiate(). void @@ -79,122 +82,135 @@ bool Ssl::PeerConnector::prepareSocket() { const int fd = serverConnection()->fd; if (!Comm::IsConnOpen(serverConn) || fd_table[serverConn->fd].closing()) { connectionClosed("Ssl::PeerConnector::prepareSocket"); return false; } // watch for external connection closures typedef CommCbMemFunT Dialer; closeHandler = JobCallback(9, 5, Dialer, this, Ssl::PeerConnector::commCloseHandler); comm_add_close_handler(fd, closeHandler); return true; } void Ssl::PeerConnector::initializeSsl() { - SSL *ssl; SSL_CTX *sslContext = NULL; const CachePeer *peer = serverConnection()->getPeer(); const int fd = serverConnection()->fd; if (peer) { assert(peer->use_ssl); sslContext = peer->sslContext; } else { sslContext = ::Config.ssl_client.sslContext; } assert(sslContext); - if ((ssl = SSL_new(sslContext)) == NULL) { + SSL *ssl = Ssl::CreateClient(sslContext, fd, "server https start"); + if (!ssl) { ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw()); anErr->xerrno = errno; debugs(83, DBG_IMPORTANT, "Error allocating SSL handle: " << ERR_error_string(ERR_get_error(), NULL)); bail(anErr); return; } - SSL_set_fd(ssl, fd); - if (peer) { if (peer->ssldomain) SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain); #if NOT_YET else if (peer->name) SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name); #endif else SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host); if (peer->sslSession) SSL_set_session(ssl, peer->sslSession); + } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) { + // client connection is required for Peek or Stare mode in the case we need to splice + // or terminate client and server connections + assert(clientConn != NULL); + SSL *clientSsl = fd_table[request->clientConnectionManager->clientConnection->fd].ssl; + BIO *b = SSL_get_rbio(clientSsl); + Ssl::ClientBio *clnBio = static_cast(b->ptr); + const Ssl::Bio::sslFeatures &features = clnBio->getFeatures(); + if (features.sslVersion != -1) { + features.applyToSSL(ssl); + // 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 { // While we are peeking at the certificate, we may not know the server // name that the client will request (after interception or CONNECT) // unless it was the CONNECT request with a user-typed address. const char *hostname = request->GetHost(); const bool hostnameIsIp = request->GetHostIsNumeric(); const bool isConnectRequest = request->clientConnectionManager.valid() && !request->clientConnectionManager->port->flags.isIntercepted(); if (!request->flags.sslPeek || isConnectRequest) SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname); // Use SNI TLS extension only when we connect directly // to the origin server and we know the server host name. if (!hostnameIsIp) Ssl::setClientSNI(ssl, hostname); } // If CertValidation Helper used do not lookup checklist for errors, // but keep a list of errors to send it to CertValidator if (!Ssl::TheConfig.ssl_crt_validator) { // Create the ACL check list now, while we have access to more info. // The list is used in ssl_verify_cb() and is freed in ssl_free(). if (acl_access *acl = ::Config.ssl_client.cert_error) { ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); // check->fd(fd); XXX: need client FD here SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check); } } // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE X509 *peeked_cert; if (request->clientConnectionManager.valid() && request->clientConnectionManager->serverBump() && (peeked_cert = request->clientConnectionManager->serverBump()->serverCert.get())) { CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509); SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert); } - - fd_table[fd].ssl = ssl; - fd_table[fd].read_method = &ssl_read_method; - fd_table[fd].write_method = &ssl_write_method; } void Ssl::PeerConnector::setReadTimeout() { int timeToRead; if (negotiationTimeout) { const int timeUsed = squid_curtime - startTime; const int timeLeft = max(0, static_cast(negotiationTimeout - timeUsed)); timeToRead = min(static_cast(::Config.Timeout.read), timeLeft); } else timeToRead = ::Config.Timeout.read; AsyncCall::Pointer nil; commSetConnTimeout(serverConnection(), timeToRead, nil); } void Ssl::PeerConnector::negotiateSsl() { if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing()) @@ -245,40 +261,102 @@ return; } catch (const std::exception &e) { debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " << "request for " << validationRequest.domainName << " certificate: " << e.what() << "; will now block to " << "validate that certificate."); // fall through to do blocking in-process generation. ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); bail(anErr); if (serverConnection()->getPeer()) { peerConnectFailed(serverConnection()->getPeer()); } serverConn->close(); return; } } callBack(); } +void switchToTunnel(HttpRequest *request, int *status_ptr, Comm::ConnectionPointer & clientConn, Comm::ConnectionPointer &srvConn); + +void +Ssl::PeerConnector::cbCheckForPeekAndSplice(allow_t answer, void *data) +{ + Ssl::PeerConnector *peerConnect = (Ssl::PeerConnector *) data; + peerConnect->checkForPeekAndSplice(true, (Ssl::BumpMode)answer.kind); +} + +bool +Ssl::PeerConnector::checkForPeekAndSplice(bool checkDone, Ssl::BumpMode peekMode) +{ + SSL *ssl = fd_table[serverConn->fd].ssl; + // Mark Step3 of bumping + if (request->clientConnectionManager.valid()) { + if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { + serverBump->step = Ssl::bumpStep3; + if (!serverBump->serverCert.get()) + serverBump->serverCert.reset(SSL_get_peer_certificate(ssl)); + } + } + + if (!checkDone) { + ACLFilledChecklist *acl_checklist = new ACLFilledChecklist( + ::Config.accessList.ssl_bump, + request.getRaw(), NULL); + acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSplice, this); + return false; + } + + 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); + + // bump, peek, stare, server-first,client-first are all mean bump the connection + if (peekMode < Ssl::bumpSplice) + peekMode = Ssl::bumpBump; + + if (peekMode == Ssl::bumpSplice && !srvBio->canSplice()) + peekMode = Ssl::bumpPeek; + else if (peekMode == Ssl::bumpBump && !srvBio->canBump()) + peekMode = Ssl::bumpSplice; + + if (peekMode == Ssl::bumpTerminate) { + 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); + Comm::SetSelect(serverConn->fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0); + debugs(83,5, "Retry the fwdNegotiateSSL on FD " << serverConn->fd); + return true; + } else { + static int status_code = 0; + debugs(83,5, "Revert to tunnel FD " << clientConn->fd << " with FD " << serverConn->fd); + switchToTunnel(request.getRaw(), &status_code, clientConn, serverConn); + return false; + } + return false; +} + void Ssl::PeerConnector::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse) { Ssl::PeerConnector *connector = (Ssl::PeerConnector *)(data); connector->sslCrtvdHandleReply(validationResponse); } void Ssl::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse const &validationResponse) { Ssl::CertErrors *errs = NULL; Ssl::ErrorDetail *errDetails = NULL; bool validatorFailed = false; if (!Comm::IsConnOpen(serverConnection())) { return; } debugs(83,5, request->GetHost() << " cert validation result: " << validationResponse.resultCode); if (validationResponse.resultCode == HelperReply::Error) @@ -376,62 +454,82 @@ return errs; } /// A wrapper for Comm::SetSelect() notifications. void Ssl::PeerConnector::NegotiateSsl(int, void *data) { PeerConnector *pc = static_cast(data); // Use job calls to add done() checks and other job logic/protections. CallJobHere(83, 7, pc, Ssl::PeerConnector, negotiateSsl); } void Ssl::PeerConnector::handleNegotiateError(const int ret) { const int fd = serverConnection()->fd; unsigned long ssl_lib_error = SSL_ERROR_NONE; SSL *ssl = fd_table[fd].ssl; int ssl_error = SSL_get_error(ssl, ret); + BIO *b = SSL_get_rbio(ssl); + Ssl::ServerBio *srvBio = static_cast(b->ptr); #ifdef EPROTO int sysErrNo = EPROTO; #else int sysErrNo = EACCES; #endif switch (ssl_error) { case SSL_ERROR_WANT_READ: setReadTimeout(); Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0); return; case SSL_ERROR_WANT_WRITE: + 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; + } Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0); return; case SSL_ERROR_SSL: case SSL_ERROR_SYSCALL: ssl_lib_error = ERR_get_error(); + // If we are in peek-and-splice mode and still we did not write to + // server yet, try to see if we should splice. + // In this case the connection can be saved. + // 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::bumpPeek || request->clientConnectionManager->sslBumpMode == Ssl::bumpStare) && srvBio->holdWrite()) { + debugs(81, 3, "Error (" << ERR_error_string(ssl_lib_error, NULL) << ") but, hold write on SSL connection on FD " << fd); + checkForPeekAndSplice(false, Ssl::bumpNone); + return; + } +#endif + // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1 if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0) sysErrNo = errno; debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd << ": " << ERR_error_string(ssl_lib_error, NULL) << " (" << ssl_error << "/" << ret << "/" << errno << ")"); break; // proceed to the general error handling code default: break; // no special error handling for all other errors } ErrorState *const anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw()); anErr->xerrno = sysErrNo; Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail); if (errFromFailure != NULL) { // The errFromFailure is attached to the ssl object === modified file 'src/ssl/PeerConnector.h' --- src/ssl/PeerConnector.h 2014-07-11 00:15:30 +0000 +++ src/ssl/PeerConnector.h 2014-08-11 16:47:47 +0000 @@ -12,40 +12,41 @@ * sources; see the CREDITS file for full details. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #ifndef SQUID_SSL_PEER_CONNECTOR_H #define SQUID_SSL_PEER_CONNECTOR_H +#include "acl/Acl.h" #include "base/AsyncCbdataCalls.h" #include "base/AsyncJob.h" #include "ssl/support.h" #include class HttpRequest; class ErrorState; namespace Ssl { class ErrorDetail; class CertValidationResponse; /// PeerConnector results (supplied via a callback). /// The connection to peer was secured if and only if the error member is nil. class PeerConnectorAnswer { public: ~PeerConnectorAnswer(); ///< deletes error if it is still set @@ -86,97 +87,104 @@ * This job never closes the connection, even on errors. If a 3rd-party * closes the connection, this job simply quits without informing the caller. */ class PeerConnector: virtual public AsyncJob { public: /// Callback dialier API to allow PeerConnector to set the answer. class CbDialer { public: virtual ~CbDialer() {} /// gives PeerConnector access to the in-dialer answer virtual PeerConnectorAnswer &answer() = 0; }; typedef RefCount HttpRequestPointer; public: PeerConnector(HttpRequestPointer &aRequest, const Comm::ConnectionPointer &aServerConn, + const Comm::ConnectionPointer &aClientConn, AsyncCall::Pointer &aCallback, const time_t timeout = 0); virtual ~PeerConnector(); protected: // AsyncJob API virtual void start(); virtual bool doneAll() const; virtual void swanSong(); virtual const char *status() const; /// The comm_close callback handler. void commCloseHandler(const CommCloseCbParams ¶ms); /// Inform us that the connection is closed. Does the required clean-up. void connectionClosed(const char *reason); /// Sets up TCP socket-related notification callbacks if things go wrong. /// If socket already closed return false, else install the comm_close /// handler to monitor the socket. bool prepareSocket(); /// Sets the read timeout to avoid getting stuck while reading from a /// silent server void setReadTimeout(); void initializeSsl(); ///< Initializes SSL state /// Performs a single secure connection negotiation step. /// It is called multiple times untill the negotiation finish or aborted. void negotiateSsl(); + bool checkForPeekAndSplice(bool, Ssl::BumpMode); + /// Called when the SSL negotiation step aborted because data needs to /// be transferred to/from SSL server or on error. In the first case /// setups the appropriate Comm::SetSelect handler. In second case /// fill an error and report to the PeerConnector caller. void handleNegotiateError(const int result); private: PeerConnector(const PeerConnector &); // not implemented PeerConnector &operator =(const PeerConnector &); // not implemented /// mimics FwdState to minimize changes to FwdState::initiate/negotiateSsl Comm::ConnectionPointer const &serverConnection() const { return serverConn; } void bail(ErrorState *error); ///< Return an error to the PeerConnector caller /// Callback the caller class, and pass the ready to communicate secure /// connection or an error if PeerConnector failed. void callBack(); /// Process response from cert validator helper void sslCrtvdHandleReply(Ssl::CertValidationResponse const &); /// Check SSL errors returned from cert validator against sslproxy_cert_error access list Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&); /// Callback function called when squid receive message from cert validator helper static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &); /// A wrapper function for negotiateSsl for use with Comm::SetSelect static void NegotiateSsl(int fd, void *data); + /// A wrapper function for checkForPeekAndSplice for use with acl + static void cbCheckForPeekAndSplice(allow_t answer, void *data); + HttpRequestPointer request; ///< peer connection trigger or cause Comm::ConnectionPointer serverConn; ///< TCP connection to the peer + Comm::ConnectionPointer clientConn; ///< TCP connection to the client AsyncCall::Pointer callback; ///< we call this with the results AsyncCall::Pointer closeHandler; ///< we call this when the connection closed time_t negotiationTimeout; ///< the ssl connection timeout to use time_t startTime; ///< when the peer connector negotiation started CBDATA_CLASS2(PeerConnector); }; std::ostream &operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &a); } // namespace Ssl #endif /* SQUID_PEER_CONNECTOR_H */ === modified file 'src/ssl/ServerBump.cc' --- src/ssl/ServerBump.cc 2013-08-15 22:09:07 +0000 +++ src/ssl/ServerBump.cc 2014-08-11 16:47:47 +0000 @@ -1,39 +1,41 @@ /* * DEBUG: section 33 Client-side Routines * */ #include "squid.h" #include "client_side.h" #include "FwdState.h" #include "ssl/ServerBump.h" #include "Store.h" #include "StoreClient.h" #include "URL.h" CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerBump); -Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e): +Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e, Ssl::BumpMode md): request(fakeRequest), - sslErrors(NULL) + sslErrors(NULL), + mode(md), + step(bumpStep1) { debugs(33, 4, HERE << "will peek at " << request->GetHost() << ':' << request->port); const char *uri = urlCanonical(request.getRaw()); if (e) { entry = e; entry->lock("Ssl::ServerBump"); } else entry = storeCreateEntry(uri, uri, request->flags, request->method); // We do not need to be a client because the error contents will be used // later, but an entry without any client will trim all its contents away. sc = storeClientListAdd(entry, this); } Ssl::ServerBump::~ServerBump() { debugs(33, 4, HERE << "destroying"); if (entry) { debugs(33, 4, HERE << *entry); storeUnregister(sc, entry, this); entry->unlock("Ssl::ServerBump"); === modified file 'src/ssl/ServerBump.h' --- src/ssl/ServerBump.h 2013-06-06 13:53:16 +0000 +++ src/ssl/ServerBump.h 2014-08-11 16:47:47 +0000 @@ -3,38 +3,41 @@ #include "base/AsyncJob.h" #include "base/CbcPointer.h" #include "comm/forward.h" #include "HttpRequest.h" #include "ip/Address.h" class ConnStateData; class store_client; namespace Ssl { /** \ingroup ServerProtocolSSLAPI * Maintains bump-server-first related information. */ class ServerBump { public: - explicit ServerBump(HttpRequest *fakeRequest, StoreEntry *e = NULL); + explicit ServerBump(HttpRequest *fakeRequest, StoreEntry *e = NULL, Ssl::BumpMode mode = Ssl::bumpServerFirst); ~ServerBump(); /// faked, minimal request; required by server-side API HttpRequest::Pointer request; StoreEntry *entry; ///< for receiving Squid-generated error messages 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 + SBuf clientSni; ///< the SSL client SNI name private: store_client *sc; ///< dummy client to prevent entry trimming CBDATA_CLASS2(ServerBump); }; } // namespace Ssl #endif === added file 'src/ssl/bio.cc' --- src/ssl/bio.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/bio.cc 2014-08-25 16:43:18 +0000 @@ -0,0 +1,935 @@ +/* + * DEBUG: section 83 SSL accelerator support + * + */ + +#include "squid.h" +#include "ssl/support.h" + +/* support.cc says this is needed */ +#if USE_OPENSSL + +#include "comm.h" +#include "ip/Address.h" +#include "fde.h" +#include "globals.h" +#include "Mem.h" +#include "ssl/bio.h" + +#if HAVE_OPENSSL_SSL_H +#include +#endif + +#undef DO_SSLV23 + +#if _SQUID_WINDOWS_ +extern int socket_read_method(int, char *, int); +extern int socket_write_method(int, const char *, int); +#endif + +/* BIO callbacks */ +static int squid_bio_write(BIO *h, const char *buf, int num); +static int squid_bio_read(BIO *h, char *buf, int size); +static int squid_bio_puts(BIO *h, const char *str); +//static int squid_bio_gets(BIO *h, char *str, int size); +static long squid_bio_ctrl(BIO *h, int cmd, long arg1, void *arg2); +static int squid_bio_create(BIO *h); +static int squid_bio_destroy(BIO *data); +/* SSL callbacks */ +static void squid_ssl_info(const SSL *ssl, int where, int ret); + +/// Initialization structure for the BIO table with +/// Squid-specific methods and BIO method wrappers. +static BIO_METHOD SquidMethods = { + BIO_TYPE_SOCKET, + "squid", + squid_bio_write, + squid_bio_read, + squid_bio_puts, + NULL, // squid_bio_gets not supported + squid_bio_ctrl, + squid_bio_create, + squid_bio_destroy, + NULL // squid_callback_ctrl not supported +}; + +BIO * +Ssl::Bio::Create(const int fd, Ssl::Bio::Type type) +{ + if (BIO *bio = BIO_new(&SquidMethods)) { + BIO_int_ctrl(bio, BIO_C_SET_FD, type, fd); + return bio; + } + return NULL; +} + +void +Ssl::Bio::Link(SSL *ssl, BIO *bio) +{ + SSL_set_bio(ssl, bio, bio); // cannot fail + SSL_set_info_callback(ssl, &squid_ssl_info); // does not provide diagnostic +} + + +Ssl::Bio::Bio(const int anFd): fd_(anFd) +{ + debugs(83, 7, "Bio constructed, this=" << this << " FD " << fd_); +} + +Ssl::Bio::~Bio() +{ + debugs(83, 7, "Bio destructing, this=" << this << " FD " << fd_); +} + +int Ssl::Bio::write(const char *buf, int size, BIO *table) +{ + errno = 0; +#if _SQUID_WINDOWS_ + const int result = socket_write_method(fd_, buf, size); +#else + const int result = default_write_method(fd_, buf, size); +#endif + const int xerrno = errno; + debugs(83, 5, "FD " << fd_ << " wrote " << result << " <= " << size); + + BIO_clear_retry_flags(table); + if (result < 0) { + const bool ignoreError = ignoreErrno(xerrno) != 0; + debugs(83, 5, "error: " << xerrno << " ignored: " << ignoreError); + if (ignoreError) + BIO_set_retry_write(table); + } + + return result; +} + +int +Ssl::Bio::read(char *buf, int size, BIO *table) +{ + errno = 0; +#if _SQUID_WINDOWS_ + const int result = socket_read_method(fd_, buf, size); +#else + const int result = default_read_method(fd_, buf, size); +#endif + const int xerrno = errno; + debugs(83, 5, "FD " << fd_ << " read " << result << " <= " << size); + + BIO_clear_retry_flags(table); + if (result < 0) { + const bool ignoreError = ignoreErrno(xerrno) != 0; + debugs(83, 5, "error: " << xerrno << " ignored: " << ignoreError); + if (ignoreError) + BIO_set_retry_read(table); + } + + return result; +} + +/// Called whenever the SSL connection state changes, an alert appears, or an +/// error occurs. See SSL_set_info_callback(). +void +Ssl::Bio::stateChanged(const SSL *ssl, int where, int ret) +{ + // Here we can use (where & STATE) to check the current state. + // Many STATE values are possible, including: SSL_CB_CONNECT_LOOP, + // SSL_CB_ACCEPT_LOOP, SSL_CB_HANDSHAKE_START, and SSL_CB_HANDSHAKE_DONE. + // For example: + // if (where & SSL_CB_HANDSHAKE_START) + // debugs(83, 9, "Trying to establish the SSL connection"); + // else if (where & SSL_CB_HANDSHAKE_DONE) + // debugs(83, 9, "SSL connection established"); + + debugs(83, 7, "FD " << fd_ << " now: 0x" << std::hex << where << std::dec << ' ' << + SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")"); +} + +bool +Ssl::ClientBio::isClientHello(int state) +{ + return (state == SSL2_ST_GET_CLIENT_HELLO_A || + 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 + ); +} + +void +Ssl::ClientBio::stateChanged(const SSL *ssl, int where, int ret) +{ + Ssl::Bio::stateChanged(ssl, where, ret); +} + +int +Ssl::ClientBio::write(const char *buf, int size, BIO *table) +{ + if (holdWrite_) { + BIO_set_retry_write(table); + return 0; + } + + 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) { + + if (rbuf.isNull()) + rbuf.init(1024, 16384); + + size = rbuf.spaceSize() > size ? size : rbuf.spaceSize(); + + if (!size) + return 0; + + int bytes = Ssl::Bio::read(buf, size, table); + if (!bytes) + return 0; + rbuf.append(buf, bytes); + debugs(83, 7, "rbuf size: " << rbuf.contentSize()); + } + + if (helloState == atHelloNone) { + + const unsigned char *head = (const unsigned char *)rbuf.content(); + const char *s = objToString(head, rbuf.contentSize()); + debugs(83, 7, "SSL Header: " << s); + if (rbuf.contentSize() < 5) { + BIO_set_retry_read(table); + return 0; + } + + if (head[0] == 0x16) { + debugs(83, 7, "SSL version 3 handshake message"); + helloSize = (head[3] << 8) + head[4]; + debugs(83, 7, "SSL Header Size: " << helloSize); + helloSize +=5; +#if defined(DO_SSLV23) + } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) { + debugs(83, 7, "SSL version 2 handshake message with v3 support"); + helloSize = head[1]; + helloSize +=5; +#endif + } else { + debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)"); + 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; + } + features.get((const unsigned char *)rbuf.content()); + helloState = atHelloReceived; + } + + if (holdRead_) { + debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)"); + BIO_set_retry_read(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); + } + + return -1; +} + +void +Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret) +{ + Ssl::Bio::stateChanged(ssl, where, ret); +} + +void +Ssl::ServerBio::setClientFeatures(const Ssl::Bio::sslFeatures &features) +{ + clientFeatures.sslVersion = features.sslVersion; + clientFeatures.compressMethod = features.compressMethod; + clientFeatures.serverName = features.serverName; + clientFeatures.clientRequestedCiphers = features.clientRequestedCiphers; + clientFeatures.unknownCiphers = features.unknownCiphers; + memcpy(clientFeatures.client_random, features.client_random, SSL3_RANDOM_SIZE); + clientFeatures.helloMessage.clear(); + clientFeatures.helloMessage.append(features.helloMessage.rawContent(), features.helloMessage.length()); + clientFeatures.doHeartBeats = features.doHeartBeats; + clientFeatures.extensions = features.extensions; + featuresSet = true; +}; + +int +Ssl::ServerBio::read(char *buf, int size, BIO *table) +{ + int bytes = Ssl::Bio::read(buf, size, table); + + if (bytes > 0 && record_) { + if (rbuf.isNull()) + rbuf.init(1024, 16384); + rbuf.append(buf, bytes); + debugs(83, 5, "Record is enabled store " << bytes << " bytes"); + } + debugs(83, 5, "Read " << bytes << " from " << size << " bytes"); + return bytes; +} + + +// This function makes the required checks to examine if the client hello +// message is compatible with the features provided by OpenSSL toolkit. +// If the features are compatible and can be supported it tries to rewrite SSL +// structure members, to replace the hello message created by openSSL, with the +// web client SSL hello message. +// 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) +{ +#if SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK + if (!ssl->s3) { + debugs(83, 5, "No SSLv3 data found!"); + return 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; + } + + // 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) { + debugs(83, 5, "Client Hello Data supports HeartBeats but we do not support!"); + return false; + } +#endif + + for (std::list::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 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, +#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; + } + } + + SSL3_BUFFER *wb=&(ssl->s3->wbuf); + if (wb->len < (size_t)features.helloMessage.length()) + 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(); + + size_t mainHelloSize = features.helloMessage.length() - 5; + const char *mainHello = features.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); + ssl->init_num = mainHelloSize; + ssl->s3->wpend_ret = mainHelloSize; + ssl->s3->wpend_tot = mainHelloSize; + return true; +#else + return false; +#endif +} + +int +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); + 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()); + + SSL *ssl = fd_table[fd_].ssl; + if (featuresSet && 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*/ + 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"); + } + } + } + } + // If we do not build any hello message, copy the current + if (helloMsg.isEmpty()) + helloMsg.append(buf, size); + + helloBuild = true; + helloMsgSize = helloMsg.length(); + //allowBump = true; + + if (allowSplice) { + // Do not write yet..... + BIO_set_retry_write(table); + return -1; + } + } + + if (!helloMsg.isEmpty()) { + debugs(83, 7, "buffered write for FD " << fd_); + int ret = Ssl::Bio::write(helloMsg.rawContent(), helloMsg.length(), table); + helloMsg.consume(ret); + if (!helloMsg.isEmpty()) { + // We need to retry sendind data. + // Say to openSSL to retry sending hello message + BIO_set_retry_write(table); + return -1; + } + + // Sending hello message complete. Do not send more data for now... + holdWrite_ = true; + + // spoof openSSL that we write what it ask us to write + return size; + } else + return Ssl::Bio::write(buf, size, table); +} + +void +Ssl::ServerBio::flush(BIO *table) +{ + if (!helloMsg.isEmpty()) { + int ret = Ssl::Bio::write(helloMsg.rawContent(), helloMsg.length(), table); + helloMsg.consume(ret); + } +} + +/// initializes BIO table after allocation +static int +squid_bio_create(BIO *bi) +{ + 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; + return 1; +} + +/// cleans BIO table before deallocation +static int +squid_bio_destroy(BIO *table) +{ + delete static_cast(table->ptr); + table->ptr = NULL; + return 1; +} + +/// wrapper for Bio::write() +static int +squid_bio_write(BIO *table, const char *buf, int size) +{ + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + return bio->write(buf, size, table); +} + +/// wrapper for Bio::read() +static int +squid_bio_read(BIO *table, char *buf, int size) +{ + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + return bio->read(buf, size, table); +} + +/// implements puts() via write() +static int +squid_bio_puts(BIO *table, const char *str) +{ + assert(str); + return squid_bio_write(table, str, strlen(str)); +} + +/// other BIO manipulations (those without dedicated callbacks in BIO table) +static long +squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2) +{ + debugs(83, 5, table << ' ' << cmd << '(' << arg1 << ", " << arg2 << ')'); + + switch (cmd) { + case BIO_C_SET_FD: { + assert(arg2); + const int fd = *static_cast(arg2); + Ssl::Bio *bio; + if (arg1 == Ssl::Bio::BIO_TO_SERVER) + bio = new Ssl::ServerBio(fd); + else + bio = new Ssl::ClientBio(fd); + assert(!table->ptr); + table->ptr = bio; + table->init = 1; + return 0; + } + + case BIO_C_GET_FD: + if (table->init) { + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + if (arg2) + *static_cast(arg2) = bio->fd(); + return bio->fd(); + } + return -1; + + case BIO_CTRL_DUP: + // Should implemented if the SSL_dup openSSL API function + // used anywhere in squid. + return 0; + + case BIO_CTRL_FLUSH: + if (table->init) { + Ssl::Bio *bio = static_cast(table->ptr); + assert(bio); + bio->flush(table); + return 1; + } + return 0; + + /* we may also need to implement these: + case BIO_CTRL_RESET: + case BIO_C_FILE_SEEK: + case BIO_C_FILE_TELL: + case BIO_CTRL_INFO: + case BIO_CTRL_GET_CLOSE: + case BIO_CTRL_SET_CLOSE: + case BIO_CTRL_PENDING: + case BIO_CTRL_WPENDING: + */ + default: + return 0; + + } + + return 0; /* NOTREACHED */ +} + +/// wrapper for Bio::stateChanged() +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(table->ptr)) + bio->stateChanged(ssl, where, ret); + } +} + +Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1), unknownCiphers(false), doHeartBeats(true) +{ + 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 (ssl->session->compress_meth) + compressMethod = ssl->session->compress_meth; + else if (sslVersion >= 3) //if it is 3 or newer version then compression is disabled + 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 + return true; +} + +bool +Ssl::Bio::sslFeatures::get(const unsigned char *hello) +{ + // The SSL handshake message should starts with a 0x16 byte + if (hello[0] == 0x16) { + return parseV3Hello(hello); +#if defined(DO_SSLV23) + } else if ((hello[0] & 0x80) && hello[2] == 0x01 && hello[3] == 0x03) { + return parseV23Hello(hello); +#endif + } + + debugs(83, 7, "Not a known SSL handshake message"); + return false; +} + +bool +Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello) +{ + debugs(83, 7, "Get fake features from v3 hello message."); + // The SSL version exist in the 2nd and 3rd bytes + sslVersion = (hello[1] << 8) | hello[2]; + debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion); + + // The following hello message size exist in 4th and 5th bytes + int helloSize = (hello[3] << 8) | hello[4]; + helloSize += 5; //Include the 5 header bytes. + helloMessage.clear(); + helloMessage.append((const char *)hello, helloSize); + + //For SSLv3 or TLSv1.* protocols we can get some more informations + if (hello[1] == 0x3 && hello[5] == 0x1 /*HELLO A message*/) { + // Get the correct version of the sub-hello message + sslVersion = (hello[9] << 8) | hello[10]; + //Get Client Random number. It starts on the position 11 of hello message + memcpy(client_random, hello + 11, SSL3_RANDOM_SIZE); + debugs(83, 7, "Client random: " << objToString(client_random, SSL3_RANDOM_SIZE)); + + // At the position 43 (11+SSL3_RANDOM_SIZE) + int sessIDLen = (int)hello[43]; + debugs(83, 7, "Session ID Length: " << sessIDLen); + + //Ciphers list. It is stored after the Session ID. + const unsigned char *ciphers = hello + 44 + sessIDLen; + int ciphersLen = (ciphers[0] << 8) | ciphers[1]; + ciphers += 2; + if (ciphersLen) { + const SSL_METHOD *method = SSLv3_method(); + int cs = method->put_cipher_by_char(NULL, NULL); + assert(cs > 0); + for (int i = 0; i < ciphersLen; i += cs) { + 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: " << (int)compression[0]); + + const unsigned char *pToExtensions = compression + 1 + (int)compression[0]; + if (pToExtensions < hello + helloSize) { + int extensionsLen = (pToExtensions[0] << 8) | pToExtensions[1]; + const unsigned char *ext = pToExtensions + 2; + while (ext < pToExtensions + extensionsLen) { + short extType = (ext[0] << 8) | ext[1]; + ext += 2; + short extLen = (ext[0] << 8) | ext[1]; + ext += 2; + debugs(83, 7, "SSL Exntension: " << std::hex << extType << " of size:" << extLen); + //The SNI extension has the type 0 (extType == 0) + // 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) { + int hostLen = (ext[3] << 8) | ext[4]; + serverName.assign((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 + doHeartBeats = true; + } else + extensions.push_back(extType); + + ext += extLen; + } + } + } + return true; +} + +bool +Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello) +{ +#if defined(DO_SSLV23) + debugs(83, 7, "Get fake features from v23 hello message."); + sslVersion = (hello[3] << 8) | hello[4]; + debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion); + + // The following hello message size exist in 2nd byte + int helloSize = hello[1]; + helloSize += 2; //Include the 2 header bytes. + helloMessage.clear(); + helloMessage.append((char *)hello, helloSize); + + //Ciphers list. It is stored after the Session ID. + + int ciphersLen = (hello[5] << 8) | hello[6]; + const unsigned char *ciphers = hello + 11; + if (ciphersLen) { + const SSL_METHOD *method = SSLv23_method(); + int cs = method->put_cipher_by_char(NULL, NULL); + assert(cs > 0); + for (int i = 0; i < ciphersLen; i += cs) { + // 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); + + //Get Client Random number. It starts on the position 11 of hello message + memcpy(client_random, ciphers + ciphersLen, SSL3_RANDOM_SIZE); + debugs(83, 7, "Client random: " << objToString(client_random, SSL3_RANDOM_SIZE)); + + compressMethod = 0; + return true; +#else + return false; +#endif +} + +void +Ssl::Bio::sslFeatures::applyToSSL(SSL *ssl) const +{ + // 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. + // For example will prevent comunnicating with a tls1.0 server if the + // client sent and tlsv1.2 Hello message. + //SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion())); +#if defined(TLSEXT_NAMETYPE_host_name) + if (!serverName.isEmpty()) { + SSL_set_tlsext_host_name(ssl, serverName.c_str()); + } +#endif + if (!clientRequestedCiphers.empty()) + SSL_set_cipher_list(ssl, clientRequestedCiphers.c_str()); +#if defined(SSL_OP_NO_COMPRESSION) /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */ + if (compressMethod == 0) + SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); +#endif + +} + +std::ostream & +Ssl::Bio::sslFeatures::print(std::ostream &os) const +{ + static std::string buf; + 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 */ === added file 'src/ssl/bio.h' --- src/ssl/bio.h 1970-01-01 00:00:00 +0000 +++ src/ssl/bio.h 2014-08-25 16:36:09 +0000 @@ -0,0 +1,194 @@ +#ifndef SQUID_SSL_BIO_H +#define SQUID_SSL_BIO_H + +#include "fd.h" +#include "SBuf.h" + +#include +#include +#if HAVE_OPENSSL_BIO_H +#include +#endif +#include + +namespace Ssl +{ + +/// BIO source and sink node, handling socket I/O and monitoring SSL state +class Bio +{ +public: + enum Type { + BIO_TO_CLIENT = 6000, + BIO_TO_SERVER + }; + + /// Class to store SSL connection features + class sslFeatures + { + public: + sslFeatures(); + bool get(const SSL *ssl); ///< Retrieves the features from SSL object + bool get(const unsigned char *hello); ///< Retrieves the features from raw SSL hello message + bool parseV3Hello(const unsigned char *hello); + bool parseV23Hello(const unsigned char *hello); + /// Prints to os stream a human readable form of sslFeatures object + std::ostream & print(std::ostream &os) const; + /// Converts to the internal squid SSL version form the sslVersion + int toSquidSSLVersion() const; + /// Configure the SSL object with the SSL features of the sslFeatures object + void applyToSSL(SSL *ssl) const; + public: + int sslVersion; ///< The requested/used SSL version + int compressMethod; ///< The requested/used compressed method + mutable SBuf serverName; ///< The SNI hostname, if any + std::string clientRequestedCiphers; ///< The client requested ciphers + bool unknownCiphers; ///< True if one or more ciphers are unknown + std::string ecPointFormatList;///< tlsExtension ecPointFormatList + std::string ellipticCurves; ///< tlsExtension ellipticCurveList + std::string opaquePrf; ///< tlsExtension opaquePrf + bool doHeartBeats; + /// The client random number + unsigned char client_random[SSL3_RANDOM_SIZE]; + std::list extensions; + SBuf helloMessage; + }; + explicit Bio(const int anFd); + virtual ~Bio(); + + /// Writes the given data to socket + virtual int write(const char *buf, int size, BIO *table); + + /// Reads data from socket + virtual int read(char *buf, int size, BIO *table); + + /// Flushes any buffered data to socket. + /// The Ssl::Bio does not buffer any data, so this method has nothing to do + virtual void flush(BIO *table) {} + + int fd() const { return fd_; } ///< The SSL socket descriptor + + /// Called by linked SSL connection whenever state changes, an alert + /// appears, or an error occurs. See SSL_set_info_callback(). + virtual void stateChanged(const SSL *ssl, int where, int ret); + + /// 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); + /// 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 +/// If bumping is enabled this Bio detects and analyses client hello message +/// to retrieve the SSL features supported by the client +class ClientBio: public Bio +{ +public: + /// The ssl hello message read states + typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived} HelloReadState; + explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), helloState(atHelloNone), helloSize(0) {} + + /// The ClientBio version of the Ssl::Bio::stateChanged method + /// When the client hello message retrieved, fill the + /// "features" member with the client provided informations. + virtual void stateChanged(const SSL *ssl, int where, int ret); + /// The ClientBio version of the Ssl::Bio::write method + virtual int write(const char *buf, int size, BIO *table); + /// The ClientBio version of the Ssl::Bio::read method + /// If the holdRead flag is true then it does not write any data + /// 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() {return features.sslVersion != -1;} + /// Return the SSL features requested by SSL client + const Bio::sslFeatures &getFeatures() const {return features;} + /// Prevents or allow writting on socket. + void hold(bool h) {holdRead_ = holdWrite_ = h;} + +private: + /// True if the SSL state corresponds to a hello message + bool isClientHello(int state); + /// The futures retrieved from client SSL hello message + Bio::sslFeatures features; + bool holdRead_; ///< The read hold state of the bio. + bool holdWrite_; ///< The write hold state of the bio. + HelloReadState helloState; ///< The SSL hello read state + int helloSize; ///< The SSL hello message sent by client size +}; + +/// BIO node to handle socket IO for squid server side +/// If bumping is enabled, analyses the SSL hello message sent by squid OpenSSL +/// subsystem (step3 bumping step) against bumping mode: +/// * Peek mode: Send client hello message instead of the openSSL generated +/// hello message and normaly denies bumping and allow only +/// splice or terminate the SSL connection +/// * Stare mode: Sends the openSSL generated hello message and normaly +/// denies splicing and allow bump or terminate the SSL +/// connection +/// If SQUID_USE_OPENSSL_HELLO_OVERWRITE_HACK is enabled also checks if the +/// openSSL library features are compatible with the features reported in +/// web client SSL hello message and if it is, overwrites the openSSL SSL +/// object members to replace hello message with web client hello message. +/// This is may allow bumping in peek mode and splicing in stare mode after +/// the server hello message received. +class ServerBio: public Bio +{ +public: + explicit ServerBio(const int anFd): Bio(anFd), featuresSet(false), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), record_(false), bumpMode_(bumpNone) {} + /// 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 + /// If a clientRandom number is set then rewrites the raw hello message + /// "client random" field with the provided random number. + /// It may buffer the output packets. + virtual int write(const char *buf, int size, BIO *table); + /// The ServerBio version of the Ssl::Bio::read method + /// If the record flag is set then append the data to the rbuf member + virtual int read(char *buf, int size, BIO *table); + /// The ServerBio version of the Ssl::Bio::flush method. + /// Flushes any buffered data + virtual void flush(BIO *table); + /// Sets the random number to use in client SSL HELLO message + void setClientFeatures(const sslFeatures &features); + + /// The write hold state + bool holdWrite() const {return holdWrite_;} + /// Enables or disables the write hold state + void holdWrite(bool h) {holdWrite_ = h;} + /// Enables or disables the input data recording, for internal analysis. + void recordInput(bool r) {record_ = r;} + /// Whether we can splice or not the SSL stream + bool canSplice() {return allowSplice;} + /// Whether we can bump or not the SSL stream + bool canBump() {return allowBump;} + /// The bumping mode + void mode(Ssl::BumpMode m) {bumpMode_ = m;} +private: + /// A random number to use as "client random" in client hello message + sslFeatures clientFeatures; + bool featuresSet; ///< True if the clientFeatures member is set and can be used + SBuf helloMsg; ///< Used to buffer output data. + mb_size_t helloMsgSize; + bool helloBuild; ///< True if the client hello message sent to the server + bool allowSplice; ///< True if the SSL stream can be spliced + bool allowBump; ///< True if the SSL stream can be bumped + bool holdWrite_; ///< The write hold state of the bio. + bool record_; ///< If true the input data recorded to rbuf for internal use + Ssl::BumpMode bumpMode_; +}; + +inline +std::ostream &operator <<(std::ostream &os, Ssl::Bio::sslFeatures const &f) +{ + return f.print(os); +} + +} // namespace Ssl + +#endif /* SQUID_SSL_BIO_H */ === modified file 'src/ssl/support.cc' --- src/ssl/support.cc 2014-07-21 05:39:57 +0000 +++ src/ssl/support.cc 2014-08-25 10:20:25 +0000 @@ -23,61 +23,69 @@ * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid.h" /* MS Visual Studio Projects are monolithic, so we need the following * #if to exclude the SSL code from compile process when not needed. */ #if USE_OPENSSL #include "acl/FilledChecklist.h" #include "anyp/PortCfg.h" +#include "fd.h" #include "fde.h" #include "globals.h" #include "ipc/MemMap.h" #include "SquidConfig.h" #include "SquidTime.h" +#include "ssl/bio.h" #include "ssl/Config.h" #include "ssl/ErrorDetail.h" #include "ssl/gadgets.h" #include "ssl/support.h" #include "URL.h" #include static void setSessionCallbacks(SSL_CTX *ctx); Ipc::MemMap *SslSessionCache = NULL; const char *SslSessionCacheName = "ssl_session_cache"; const char *Ssl::BumpModeStr[] = { "none", "client-first", "server-first", + "peek", + "stare", + "bump", + "splice", + "terminate", + /*"err",*/ NULL }; /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals \ingroup ServerProtocolSSLAPI */ /// \ingroup ServerProtocolSSLInternal static int ssl_ask_password_cb(char *buf, int size, int rwflag, void *userdata) { FILE *in; int len = 0; char cmdline[1024]; snprintf(cmdline, sizeof(cmdline), "\"%s\" \"%s\"", Config.Program.ssl_password, (const char *)userdata); in = popen(cmdline, "r"); if (fgets(buf, size, in)) @@ -980,106 +988,196 @@ debugs(83, 5, "Comparing private and public SSL keys."); if (!SSL_CTX_check_private_key(sslContext)) { ssl_error = ERR_get_error(); debugs(83, DBG_CRITICAL, "ERROR: SSL private key '" << certfile << "' does not match public key '" << keyfile << "': " << ERR_error_string(ssl_error, NULL)); SSL_CTX_free(sslContext); return NULL; } */ if (!configureSslContext(sslContext, port)) { debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context"); SSL_CTX_free(sslContext); return NULL; } return sslContext; } -SSL_CTX * -sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile) +int Ssl::OpenSSLtoSquidSSLVersion(int sslVersion) { - int ssl_error; - Ssl::ContextMethod method; - SSL_CTX * sslContext; - long fl = Ssl::parse_flags(flags); + 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; +} - ssl_initialize(); - if (!keyfile) - keyfile = certfile; +#if OPENSSL_VERSION_NUMBER < 0x00909000L +SSL_METHOD * +#else +const SSL_METHOD * +#endif +Ssl::method(int version) +{ + switch (version) { - if (!certfile) - certfile = keyfile; + case 2: +#if !defined(OPENSSL_NO_SSL2) + debugs(83, 5, "Using SSLv2."); + return SSLv2_client_method(); +#else + debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy."); + return NULL; +#endif + break; + + case 3: + debugs(83, 5, "Using SSLv3."); + return SSLv3_client_method(); + break; + + case 4: + debugs(83, 5, "Using TLSv1."); + return TLSv1_client_method(); + break; + case 5: +#if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. + debugs(83, 5, "Using TLSv1.1."); + return TLSv1_1_client_method(); +#else + debugs(83, DBG_IMPORTANT, "TLSv1.1 is not available in this Proxy."); + return NULL; +#endif + break; + + case 6: +#if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. + debugs(83, 5, "Using TLSv1.2"); + return TLSv1_2_client_method(); +#else + debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy."); + return NULL; +#endif + break; + + case 1: + + default: + debugs(83, 5, "Using SSLv2/SSLv3."); + return SSLv23_client_method(); + break; + } + + //Not reached + return NULL; +} + +const SSL_METHOD * +Ssl::serverMethod(int version) +{ switch (version) { case 2: #ifndef OPENSSL_NO_SSL2 debugs(83, 5, "Using SSLv2."); - method = SSLv2_client_method(); + return SSLv2_server_method(); #else debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy."); return NULL; #endif break; case 3: debugs(83, 5, "Using SSLv3."); - method = SSLv3_client_method(); + return SSLv3_server_method(); break; case 4: debugs(83, 5, "Using TLSv1."); - method = TLSv1_client_method(); + return TLSv1_server_method(); break; case 5: #if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. debugs(83, 5, "Using TLSv1.1."); - method = TLSv1_1_client_method(); + return TLSv1_1_server_method(); #else debugs(83, DBG_IMPORTANT, "TLSv1.1 is not available in this Proxy."); return NULL; #endif break; case 6: #if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. debugs(83, 5, "Using TLSv1.2"); - method = TLSv1_2_client_method(); + return TLSv1_2_server_method(); #else debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy."); return NULL; #endif break; case 1: default: debugs(83, 5, "Using SSLv2/SSLv3."); - method = SSLv23_client_method(); + return SSLv23_server_method(); break; } + //Not reached + return NULL; +} + +SSL_CTX * +sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile) +{ + int ssl_error; + Ssl::ContextMethod method; + SSL_CTX * sslContext; + long fl = Ssl::parse_flags(flags); + + ssl_initialize(); + + if (!keyfile) + keyfile = certfile; + + if (!certfile) + certfile = keyfile; + + if (!(method = Ssl::method(version))) + return NULL; + sslContext = SSL_CTX_new(method); if (sslContext == NULL) { ssl_error = ERR_get_error(); fatalf("Failed to allocate SSL context: %s\n", ERR_error_string(ssl_error, NULL)); } SSL_CTX_set_options(sslContext, Ssl::parse_options(options)); if (cipher) { debugs(83, 5, "Using chiper suite " << cipher << "."); if (!SSL_CTX_set_cipher_list(sslContext, cipher)) { ssl_error = ERR_get_error(); fatalf("Failed to set SSL cipher suite '%s': %s\n", cipher, ERR_error_string(ssl_error, NULL)); } } @@ -1435,42 +1533,42 @@ debugs(83, 5, "Using TLSv1.2"); method = TLSv1_2_server_method(); #else debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy."); return NULL; #endif break; case 1: default: debugs(83, 5, "Using SSLv2/SSLv3."); method = SSLv23_server_method(); break; } return method; } /// \ingroup ServerProtocolSSLInternal /// Create SSL context and apply ssl certificate and private key to it. -static SSL_CTX * -createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port) +SSL_CTX * +Ssl::createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port) { Ssl::SSL_CTX_Pointer sslContext(SSL_CTX_new(port.contextMethod)); if (!SSL_CTX_use_certificate(sslContext.get(), x509.get())) return NULL; if (!SSL_CTX_use_PrivateKey(sslContext.get(), pkey.get())) return NULL; if (!configureSslContext(sslContext.get(), port)) return NULL; return sslContext.release(); } SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port) { Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; @@ -1483,40 +1581,83 @@ return createSSLContext(cert, pkey, port); } SSL_CTX * Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port) { Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; if (!generateSslCertificate(cert, pkey, properties)) return NULL; if (!cert) return NULL; if (!pkey) return NULL; return createSSLContext(cert, pkey, port); } +bool +Ssl::configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!generateSslCertificate(cert, pkey, properties)) + return false; + + if (!cert) + return false; + + if (!pkey) + return false; + + if (!SSL_use_certificate(ssl, cert.get())) + return false; + + if (!SSL_use_PrivateKey(ssl, pkey.get())) + return false; + + return true; +} + +bool +Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!readCertAndPrivateKeyFromMemory(cert, pkey, data)) + return false; + + if (!cert || !pkey) + return false; + + if (!SSL_use_certificate(ssl, cert.get())) + return false; + + if (!SSL_use_PrivateKey(ssl, pkey.get())) + return false; + + return true; +} + bool Ssl::verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties) { // SSL_get_certificate is buggy in openssl versions 1.0.1d and 1.0.1e // Try to retrieve certificate directly from SSL_CTX object #if SQUID_USE_SSLGETCERTIFICATE_HACK X509 ***pCert = (X509 ***)sslContext->cert; X509 * cert = pCert && *pCert ? **pCert : NULL; #elif SQUID_SSLGETCERTIFICATE_BUGGY X509 * cert = NULL; assert(0); #else // Temporary ssl for getting X509 certificate from SSL_CTX. Ssl::SSL_Pointer ssl(SSL_new(sslContext)); X509 * cert = SSL_get_certificate(ssl.get()); #endif if (!cert) return false; ASN1_TIME * time_notBefore = X509_get_notBefore(cert); ASN1_TIME * time_notAfter = X509_get_notAfter(cert); bool ret = (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0); @@ -1625,40 +1766,82 @@ Ssl::CertificateProperties certProperties; if (const char *cn = CommonHostName(cert.get())) { certProperties.commonName = "Not trusted by \""; certProperties.commonName += cn; certProperties.commonName += "\""; } else if (const char *org = getOrganization(cert.get())) { certProperties.commonName = "Not trusted by \""; certProperties.commonName += org; certProperties.commonName += "\""; } else certProperties.commonName = "Not trusted"; certProperties.setCommonName = true; // O, OU, and other CA subject fields will be mimicked // Expiration date and other common properties will be mimicked certProperties.signAlgorithm = Ssl::algSignSelf; certProperties.signWithPkey.resetAndLock(pkey.get()); certProperties.mimicCert.resetAndLock(cert.get()); return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties); } +SSL * +SslCreate(SSL_CTX *sslContext, const int fd, Ssl::Bio::Type type, const char *squidCtx) +{ + const char *errAction = NULL; + int errCode = 0; + if (SSL *ssl = SSL_new(sslContext)) { + // without BIO, we would call SSL_set_fd(ssl, fd) instead + if (BIO *bio = Ssl::Bio::Create(fd, type)) { + Ssl::Bio::Link(ssl, bio); // cannot fail + + fd_table[fd].ssl = ssl; + fd_table[fd].read_method = &ssl_read_method; + fd_table[fd].write_method = &ssl_write_method; + fd_note(fd, squidCtx); + + return ssl; + } + errCode = ERR_get_error(); + errAction = "failed to initialize I/O"; + SSL_free(ssl); + } else { + errCode = ERR_get_error(); + errAction = "failed to allocate handle"; + } + + debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction << + ": " << ERR_error_string(errCode, NULL)); + return NULL; +} + +SSL * +Ssl::CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx) +{ + return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_SERVER, squidCtx); +} + +SSL * +Ssl::CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx) +{ + return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_CLIENT, squidCtx); +} + Ssl::CertError::CertError(ssl_error_t anErr, X509 *aCert): code(anErr) { cert.resetAndLock(aCert); } Ssl::CertError::CertError(CertError const &err): code(err.code) { cert.resetAndLock(err.cert.get()); } Ssl::CertError & Ssl::CertError::operator = (const CertError &old) { code = old.code; cert.resetAndLock(old.cert.get()); return *this; } bool Ssl::CertError::operator == (const CertError &ce) const === modified file 'src/ssl/support.h' --- src/ssl/support.h 2014-01-12 17:51:12 +0000 +++ src/ssl/support.h 2014-08-11 16:47:47 +0000 @@ -66,40 +66,48 @@ // Maximum certificate validation callbacks. OpenSSL versions exceeding this // limit are deemed stuck in an infinite validation loop (OpenSSL bug #3090) // and will trigger the SQUID_X509_V_ERR_INFINITE_VALIDATION error. // Can be set to a number up to UINT32_MAX #ifndef SQUID_CERT_VALIDATION_ITERATION_MAX #define SQUID_CERT_VALIDATION_ITERATION_MAX 16384 #endif namespace AnyP { class PortCfg; }; namespace Ssl { /// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE typedef int ssl_error_t; typedef CbDataList Errors; +/// Creates SSL Client connection structure and initializes SSL I/O (Comm and BIO). +/// On errors, emits DBG_IMPORTANT with details and returns NULL. +SSL *CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx); + +/// Creates SSL Server connection structure and initializes SSL I/O (Comm and BIO). +/// On errors, emits DBG_IMPORTANT with details and returns NULL. +SSL *CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx); + /// An SSL certificate-related error. /// Pairs an error code with the certificate experiencing the error. class CertError { public: ssl_error_t code; ///< certificate error code X509_Pointer cert; ///< certificate with the above error code CertError(ssl_error_t anErr, X509 *aCert); CertError(CertError const &err); CertError & operator = (const CertError &old); bool operator == (const CertError &ce) const; bool operator != (const CertError &ce) const; }; /// Holds a list of certificate SSL errors typedef CbDataList CertErrors; } //namespace Ssl /// \ingroup ServerProtocolSSLAPI @@ -133,41 +141,43 @@ const char *sslGetUserCertificateChainPEM(SSL *ssl); namespace Ssl { /// \ingroup ServerProtocolSSLAPI typedef char const *GETX509ATTRIBUTE(X509 *, const char *); /// \ingroup ServerProtocolSSLAPI GETX509ATTRIBUTE GetX509UserAttribute; /// \ingroup ServerProtocolSSLAPI GETX509ATTRIBUTE GetX509CAAttribute; /// \ingroup ServerProtocolSSLAPI GETX509ATTRIBUTE GetX509Fingerprint; /** \ingroup ServerProtocolSSLAPI * Supported ssl-bump modes */ -enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpEnd}; +enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpPeek, bumpStare, bumpBump, bumpSplice, bumpTerminate, /*bumpErr,*/ bumpEnd}; + +enum BumpStep {bumpStep1, bumpStep2, bumpStep3}; /** \ingroup ServerProtocolSSLAPI * Short names for ssl-bump modes */ extern const char *BumpModeStr[]; /** \ingroup ServerProtocolSSLAPI * Return the short name of the ssl-bump mode "bm" */ inline const char *bumpMode(int bm) { return (0 <= bm && bm < Ssl::bumpEnd) ? Ssl::BumpModeStr[bm] : NULL; } /** \ingroup ServerProtocolSSLAPI * Parses the SSL flags. */ @@ -210,40 +220,61 @@ SSL_CTX * generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port); /** \ingroup ServerProtocolSSLAPI * Check if the certificate of the given context is still valid \param sslContext The context to check \param properties Check if the context certificate matches the given properties \return true if the contexts certificate is valid, false otherwise */ bool verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties); /** \ingroup ServerProtocolSSLAPI * Read private key and certificate from memory and generate SSL context * using their. */ SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port); /** \ingroup ServerProtocolSSLAPI + * Create an SSL context using the provided certificate and key + */ +SSL_CTX * createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port); + +/** + \ingroup ServerProtocolSSLAPI + * Generates a certificate and a private key using provided properies and set it + * to SSL object. + */ +bool configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port); + +/** + \ingroup ServerProtocolSSLAPI + * Read private key and certificate from memory and set it to SSL object + * using their. + */ +bool configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port); + + +/** + \ingroup ServerProtocolSSLAPI * Adds the certificates in certList to the certificate chain of the SSL context */ void addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *certList); /** \ingroup ServerProtocolSSLAPI * Read certificate, private key and any certificates which must be chained from files. * See also: Ssl::readCertAndPrivateKeyFromFiles function, defined in gadgets.h * \param certFilename name of file with certificate and certificates which must be chainned. * \param keyFilename name of file with private key. */ void readCertChainAndPrivateKeyFromFiles(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, X509_STACK_Pointer & chain, char const * certFilename, char const * keyFilename); /** \ingroup ServerProtocolSSLAPI * Iterates over the X509 common and alternate names and to see if matches with given data * using the check_func. \param peer_cert The X509 cert to check \param check_data The data with which the X509 CNs compared \param check_func The function used to match X509 CNs. The CN data passed as ASN1_STRING data @@ -261,40 +292,50 @@ bool checkX509ServerValidity(X509 *cert, const char *server); /** \ingroup ServerProtocolSSLAPI * Convert a given ASN1_TIME to a string form. \param tm the time in ASN1_TIME form \param buf the buffer to write the output \param len write at most len bytes \return The number of bytes written */ int asn1timeToString(ASN1_TIME *tm, char *buf, int len); /** \ingroup ServerProtocolSSLAPI * Sets the hostname for the Server Name Indication (SNI) TLS extension * if supported by the used openssl toolkit. \return true if SNI set false otherwise */ bool setClientSNI(SSL *ssl, const char *fqdn); +int OpenSSLtoSquidSSLVersion(int sslVersion); + +#if OPENSSL_VERSION_NUMBER < 0x00909000L +SSL_METHOD *method(int version); +#else +const SSL_METHOD *method(int version); +#endif + +const SSL_METHOD *serverMethod(int version); + /** \ingroup ServerProtocolSSLAPI * Initializes the shared session cache if configured */ void initialize_session_cache(); /** \ingroup ServerProtocolSSLAPI * Destroy the shared session cache if configured */ void destruct_session_cache(); } //namespace Ssl #if _SQUID_WINDOWS_ #if defined(__cplusplus) /** \cond AUTODOCS-IGNORE */ namespace Squid { === modified file 'src/tests/stub_tunnel.cc' --- src/tests/stub_tunnel.cc 2013-11-05 22:04:30 +0000 +++ src/tests/stub_tunnel.cc 2014-08-11 16:07:45 +0000 @@ -1,10 +1,12 @@ #include "squid.h" #define STUB_API "tunnel.cc" #include "tests/STUB.h" #include "FwdState.h" class ClientHttpRequest; void tunnelStart(ClientHttpRequest *, int64_t *, int *, const AccessLogEntryPointer &al) STUB +void switchToTunnel(HttpRequest *request, int *status_ptr, Comm::ConnectionPointer &clientConn, Comm::ConnectionPointer &srvConn) STUB + === modified file 'src/tunnel.cc' --- src/tunnel.cc 2014-06-24 22:52:53 +0000 +++ src/tunnel.cc 2014-08-25 15:28:11 +0000 @@ -27,52 +27,55 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid.h" #include "acl/FilledChecklist.h" #include "base/CbcPointer.h" #include "CachePeer.h" #include "client_side.h" #include "client_side_request.h" #include "comm.h" #include "comm/Connection.h" #include "comm/ConnOpener.h" #include "comm/Read.h" #include "comm/Write.h" #include "errorpage.h" #include "fde.h" +#include "globals.h" #include "FwdState.h" #include "http.h" #include "HttpRequest.h" #include "HttpStateFlags.h" #include "ip/QosConfig.h" #include "LogTags.h" #include "MemBuf.h" #include "PeerSelectState.h" #include "SquidConfig.h" #include "StatCounters.h" #if USE_OPENSSL +#include "ssl/bio.h" #include "ssl/PeerConnector.h" +#include "ssl/ServerBump.h" #endif #include "tools.h" #if USE_DELAY_POOLS #include "DelayId.h" #endif #include #include /** * TunnelStateData is the state engine performing the tasks for * setup of a TCP tunnel from an existing open client FD to a server * then shuffling binary data between the resulting FD pair. */ /* * TODO 1: implement a read/write API on ConnStateData to send/receive blocks * of pre-formatted data. Then we can use that as the client side of the tunnel * instead of re-implementing it here and occasionally getting the ConnStateData * read/write state wrong. * @@ -102,40 +105,45 @@ bool noConnections() const; char *url; CbcPointer http; HttpRequest::Pointer request; AccessLogEntryPointer al; Comm::ConnectionList serverDestinations; const char * getHost() const { return (server.conn != NULL && server.conn->getPeer() ? server.conn->getPeer()->host : request->GetHost()); }; /// Whether we are writing a CONNECT request to a peer. bool waitingForConnectRequest() const { return connectReqWriting; } /// Whether we are reading a CONNECT response from a peer. bool waitingForConnectResponse() const { return connectRespBuf; } /// Whether we are waiting for the CONNECT request/response exchange with the peer. bool waitingForConnectExchange() const { return waitingForConnectRequest() || waitingForConnectResponse(); } /// 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)); } class Connection { public: Connection() : len (0), buf ((char *)xmalloc(SQUID_TCP_SO_RCVBUF)), size_ptr(NULL) {} ~Connection(); int bytesWanted(int lower=0, int upper = INT_MAX) const; void bytesIn(int const &); #if USE_DELAY_POOLS void setDelayId(DelayId const &); #endif void error(int const xerrno); @@ -247,40 +255,41 @@ TunnelStateData *tunnelState = (TunnelStateData *)params.data; debugs(26, 3, HERE << tunnelState->client.conn); tunnelState->client.conn = NULL; if (tunnelState->noConnections()) { delete tunnelState; return; } if (!tunnelState->client.len) { tunnelState->server.conn->close(); return; } } TunnelStateData::TunnelStateData() : url(NULL), http(), request(NULL), status_ptr(NULL), + logTag_ptr(NULL), connectRespBuf(NULL), connectReqWriting(false) { debugs(26, 3, "TunnelStateData constructed this=" << this); } TunnelStateData::~TunnelStateData() { debugs(26, 3, "TunnelStateData destructed this=" << this); assert(noConnections()); xfree(url); serverDestinations.clear(); delete connectRespBuf; } TunnelStateData::Connection::~Connection() { safe_free(buf); } @@ -694,41 +703,42 @@ void TunnelStateData::readConnectResponse() { assert(waitingForConnectResponse()); AsyncCall::Pointer call = commCbCall(5,4, "readConnectResponseDone", CommIoCbPtrFun(ReadConnectResponseDone, this)); comm_read(server.conn, connectRespBuf->space(), server.bytesWanted(1, connectRespBuf->spaceSize()), call); } /** * Set the HTTP status for this request and sets the read handlers for client * and server side connections. */ static void tunnelStartShoveling(TunnelStateData *tunnelState) { assert(!tunnelState->waitingForConnectExchange()); *tunnelState->status_ptr = Http::scOkay; - *tunnelState->logTag_ptr = LOG_TCP_TUNNEL; + if (tunnelState->logTag_ptr) + *tunnelState->logTag_ptr = LOG_TCP_TUNNEL; if (cbdataReferenceValid(tunnelState)) { // Shovel any payload already pushed into reply buffer by the server response if (!tunnelState->server.len) tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer); else { debugs(26, DBG_DATA, "Tunnel server PUSH Payload: \n" << Raw("", tunnelState->server.buf, tunnelState->server.len) << "\n----------"); tunnelState->copy(tunnelState->server.len, tunnelState->server, tunnelState->client, TunnelStateData::WriteClientDone); } // Bug 3371: shovel any payload already pushed into ConnStateData by the client request if (tunnelState->http.valid() && tunnelState->http->getConn() && !tunnelState->http->getConn()->in.buf.isEmpty()) { struct ConnStateData::In *in = &tunnelState->http->getConn()->in; debugs(26, DBG_DATA, "Tunnel client PUSH Payload: \n" << in->buf << "\n----------"); // We just need to ensure the bytes from ConnStateData are in client.buf already to deliver memcpy(tunnelState->client.buf, in->buf.rawContent(), in->buf.length()); // NP: readClient() takes care of buffer length accounting. tunnelState->readClient(tunnelState->client.buf, in->buf.length(), Comm::OK, 0); in->buf.consume(); // ConnStateData buffer accounting after the shuffle. @@ -947,41 +957,41 @@ commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall); peerSelect(&(tunnelState->serverDestinations), request, al, NULL, tunnelPeerSelectComplete, tunnelState); } void TunnelStateData::connectToPeer() { const Comm::ConnectionPointer &srv = server.conn; #if USE_OPENSSL if (CachePeer *p = srv->getPeer()) { if (p->use_ssl) { AsyncCall::Pointer callback = asyncCall(5,4, "TunnelStateData::ConnectedToPeer", MyAnswerDialer(&TunnelStateData::connectedToPeer, this)); Ssl::PeerConnector *connector = - new Ssl::PeerConnector(request, srv, callback); + new Ssl::PeerConnector(request, srv, client.conn, callback); AsyncJob::Start(connector); // will call our callback return; } } #endif tunnelRelayConnectRequest(srv, this); } #if USE_OPENSSL /// Ssl::PeerConnector callback void TunnelStateData::connectedToPeer(Ssl::PeerConnectorAnswer &answer) { if (ErrorState *error = answer.error.get()) { *status_ptr = error->httpStatus; error->callback = tunnelErrorComplete; error->callback_data = this; errorSend(client.conn, error); answer.error.clear(); // preserve error for errorSendComplete() @@ -1077,20 +1087,99 @@ cs->setHost(tunnelState->url); AsyncJob::Start(cs); } CBDATA_CLASS_INIT(TunnelStateData); bool TunnelStateData::noConnections() const { return !Comm::IsConnOpen(server.conn) && !Comm::IsConnOpen(client.conn); } #if USE_DELAY_POOLS void TunnelStateData::Connection::setDelayId(DelayId const &newDelay) { delayId = newDelay; } #endif + +#if USE_OPENSSL +void +switchToTunnel(HttpRequest *request, int *status_ptr, Comm::ConnectionPointer &clientConn, Comm::ConnectionPointer &srvConn) +{ + debugs(26, 3, HERE); + /* Create state structure. */ + TunnelStateData *tunnelState = NULL; + const char *url = urlCanonical(request); + + debugs(26, 3, request->method << " " << url << " " << request->http_ver); + ++statCounter.server.all.requests; + ++statCounter.server.other.requests; + + tunnelState = new TunnelStateData; + tunnelState->url = xstrdup(url); + tunnelState->request = request; + tunnelState->server.size_ptr = NULL; //Set later if ClientSocketContext is available + tunnelState->status_ptr = status_ptr; + tunnelState->client.conn = clientConn; + + ConnStateData *conn; + if ((conn = request->clientConnectionManager.get())) { + ClientSocketContext::Pointer context = conn->getCurrentContext(); + if (context != NULL && context->http != NULL) { + tunnelState->logTag_ptr = &context->http->logType; + tunnelState->server.size_ptr = &context->http->out.size; + +#if USE_DELAY_POOLS + /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */ + if (srvConn->getPeer() && srvConn->getPeer()->options.no_delay) + tunnelState->server.setDelayId(DelayId::DelayClient(context->http)); +#endif + } + } + + + comm_add_close_handler(tunnelState->client.conn->fd, + tunnelClientClosed, + tunnelState); + + AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout", + CommTimeoutCbPtrFun(tunnelTimeout, tunnelState)); + commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall); + fd_table[clientConn->fd].read_method = &default_read_method; + fd_table[clientConn->fd].write_method = &default_write_method; + + tunnelState->request->hier.note(srvConn, tunnelState->getHost()); + + tunnelState->server.conn = srvConn; + tunnelState->request->peer_host = srvConn->getPeer() ? srvConn->getPeer()->host : NULL; + comm_add_close_handler(srvConn->fd, tunnelServerClosed, tunnelState); + + debugs(26, 4, "determine post-connect handling pathway."); + if (srvConn->getPeer()) { + tunnelState->request->peer_login = srvConn->getPeer()->login; + tunnelState->request->flags.proxying = !(srvConn->getPeer()->options.originserver); + } else { + tunnelState->request->peer_login = NULL; + tunnelState->request->flags.proxying = false; + } + + timeoutCall = commCbCall(5, 4, "tunnelTimeout", + CommTimeoutCbPtrFun(tunnelTimeout, tunnelState)); + commSetConnTimeout(srvConn, Config.Timeout.read, timeoutCall); + 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(); + + AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone", + CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState)); + Comm::Write(tunnelState->client.conn, buf.content(), buf.contentSize(), call, NULL); +} +#endif //USE_OPENSSL