------------------------------------------------------------ revno: 13556 [merge] revision-id: chtsanti@users.sourceforge.net-20140827080525-t26dyj9ucrfg8338 parent: squid3@treenet.co.nz-20140826154843-e2czvhzpm6g8vnl2 parent: chtsanti@users.sourceforge.net-20140827073809-de9mhx852nhbhygf committer: Christos Tsantilas branch nick: trunk timestamp: Wed 2014-08-27 11:05:25 +0300 message: 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 ------------------------------------------------------------ Use --include-merges or -n0 to see merged revisions. ------------------------------------------------------------ # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: chtsanti@users.sourceforge.net-20140827080525-\ # t26dyj9ucrfg8338 # target_branch: http://bzr.squid-cache.org/bzr/squid3/trunk/ # testament_sha1: e8259c08b3bbacf4e206dbebc19bab696e0f196e # timestamp: 2014-08-27 08:59:08 +0000 # source_branch: http://bzr.squid-cache.org/bzr/squid3/trunk/ # base_revision_id: squid3@treenet.co.nz-20140826154843-\ # e2czvhzpm6g8vnl2 # # Begin patch === modified file 'acinclude/lib-checks.m4' --- acinclude/lib-checks.m4 2014-08-26 02:39:30 +0000 +++ acinclude/lib-checks.m4 2014-08-26 09:01:27 +0000 @@ -255,3 +255,45 @@ 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-08-26 02:39:30 +0000 +++ configure.ac 2014-08-26 09:01:27 +0000 @@ -1295,6 +1295,7 @@ ## 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 \ @@ -1344,6 +1345,7 @@ 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]) === modified file 'src/AclRegs.cc' --- src/AclRegs.cc 2014-08-23 10:39:15 +0000 +++ src/AclRegs.cc 2014-08-26 09:01:27 +0000 @@ -15,6 +15,10 @@ #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" @@ -160,6 +164,9 @@ 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 === modified file 'src/FwdState.cc' --- src/FwdState.cc 2014-08-23 10:39:15 +0000 +++ src/FwdState.cc 2014-08-26 09:01:27 +0000 @@ -712,7 +712,7 @@ // 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; } === modified file 'src/PeerPoolMgr.cc' --- src/PeerPoolMgr.cc 2014-07-11 00:15:30 +0000 +++ src/PeerPoolMgr.cc 2014-08-13 11:14:51 +0000 @@ -120,7 +120,7 @@ // 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; } === added file 'src/acl/AtStep.cc' --- src/acl/AtStep.cc 1970-01-01 00:00:00 +0000 +++ src/acl/AtStep.cc 2014-08-26 08:21:27 +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-26 08:21:27 +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-26 08:21:27 +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-26 08:21:27 +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 2014-08-23 10:39:15 +0000 +++ src/acl/Makefile.am 2014-08-26 09:01:27 +0000 @@ -133,6 +133,10 @@ EXTRA_libacls_la_SOURCES = SSL_ACLS = \ + AtStep.cc \ + AtStep.h \ + AtStepData.cc \ + AtStepData.h \ CertificateData.cc \ CertificateData.h \ Certificate.cc \ === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2014-08-23 10:39:15 +0000 +++ src/cache_cf.cc 2014-08-26 09:01:27 +0000 @@ -4675,6 +4675,21 @@ } 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; === modified file 'src/cf.data.depend' --- src/cf.data.depend 2014-08-23 10:39:15 +0000 +++ src/cf.data.depend 2014-08-26 09:01:27 +0000 @@ -73,6 +73,7 @@ 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-23 10:39:15 +0000 +++ src/cf.data.pre 2014-08-26 09:01:27 +0000 @@ -676,6 +676,9 @@ %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} @@ -1090,6 +1093,17 @@ # 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] @@ -2442,7 +2456,7 @@ 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 @@ -2450,51 +2464,77 @@ 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. - - ssl_bump [!]acl ... - - The following bumping modes are supported: + 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. + + bump + Establish a secure connection with the server and, using a + mimicked server certificate, with the client. + + 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 @@ -3963,6 +4003,10 @@ 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): === modified file 'src/client_side.cc' --- src/client_side.cc 2014-08-25 11:25:54 +0000 +++ src/client_side.cc 2014-08-26 09:01:27 +0000 @@ -138,6 +138,7 @@ #include "ClientInfo.h" #endif #if USE_OPENSSL +#include "ssl/bio.h" #include "ssl/context_storage.h" #include "ssl/gadgets.h" #include "ssl/helper.h" @@ -3375,32 +3376,19 @@ 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; @@ -3410,48 +3398,61 @@ 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) << @@ -3578,8 +3579,8 @@ // 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 { @@ -3687,8 +3688,16 @@ 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; } } @@ -3795,23 +3804,26 @@ 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 @@ -3833,8 +3845,15 @@ #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); @@ -3923,12 +3942,158 @@ // 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) { === modified file 'src/client_side.h' --- src/client_side.h 2014-08-23 10:39:15 +0000 +++ src/client_side.h 2014-08-26 09:01:27 +0000 @@ -352,6 +352,14 @@ /// the second part of old httpsAccept, waiting for future HttpsServer home void postHttpsAccept(); + /// 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); === modified file 'src/client_side_request.h' --- src/client_side_request.h 2014-08-23 10:39:15 +0000 +++ src/client_side_request.h 2014-08-26 09:01:27 +0000 @@ -150,7 +150,7 @@ /// 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(); === modified file 'src/external_acl.cc' --- src/external_acl.cc 2014-08-23 10:39:15 +0000 +++ src/external_acl.cc 2014-08-26 09:01:27 +0000 @@ -65,6 +65,7 @@ #include "URL.h" #include "wordlist.h" #if USE_OPENSSL +#include "ssl/ServerBump.h" #include "ssl/support.h" #endif #if USE_AUTH @@ -423,7 +424,12 @@ 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) @@ -559,6 +565,9 @@ 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::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: === modified file 'src/fd.h' --- src/fd.h 2012-09-20 11:28:21 +0000 +++ src/fd.h 2014-08-26 08:21:27 +0000 @@ -40,5 +40,7 @@ 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-08-25 11:25:54 +0000 +++ src/format/ByteCode.h 2014-08-26 09:01:27 +0000 @@ -206,6 +206,9 @@ 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, === modified file 'src/format/Format.cc' --- src/format/Format.cc 2014-08-23 10:39:15 +0000 +++ src/format/Format.cc 2014-08-26 09:01:27 +0000 @@ -17,6 +17,7 @@ #include "URL.h" #if USE_OPENSSL #include "ssl/ErrorDetail.h" +#include "ssl/ServerBump.h" #endif /// Convert a string to NULL pointer if it is "" @@ -1134,6 +1135,19 @@ } } 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: === modified file 'src/format/Token.cc' --- src/format/Token.cc 2014-08-25 11:25:54 +0000 +++ src/format/Token.cc 2014-08-26 09:01:27 +0000 @@ -181,6 +181,9 @@ {"bump_mode", LFT_SSL_BUMP_MODE}, {">cert_subject", LFT_SSL_USER_CERT_SUBJECT}, {">cert_issuer", LFT_SSL_USER_CERT_ISSUER}, + {">sni", LFT_SSL_CLIENT_SNI}, + /*{"getPeer(); const int fd = serverConnection()->fd; @@ -110,7 +112,8 @@ 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)); @@ -118,8 +121,6 @@ return; } - SSL_set_fd(ssl, fd); - if (peer) { if (peer->ssldomain) SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain); @@ -137,6 +138,25 @@ 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) @@ -174,10 +194,6 @@ 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 @@ -262,6 +278,68 @@ 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) { @@ -393,6 +471,8 @@ 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; @@ -408,6 +488,11 @@ 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; @@ -415,6 +500,19 @@ 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; === modified file 'src/ssl/PeerConnector.h' --- src/ssl/PeerConnector.h 2014-07-11 00:15:30 +0000 +++ src/ssl/PeerConnector.h 2014-08-06 10:26:14 +0000 @@ -29,6 +29,7 @@ #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" @@ -103,6 +104,7 @@ public: PeerConnector(HttpRequestPointer &aRequest, const Comm::ConnectionPointer &aServerConn, + const Comm::ConnectionPointer &aClientConn, AsyncCall::Pointer &aCallback, const time_t timeout = 0); virtual ~PeerConnector(); @@ -134,6 +136,8 @@ /// 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 @@ -165,8 +169,12 @@ /// 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 === modified file 'src/ssl/ServerBump.cc' --- src/ssl/ServerBump.cc 2014-08-23 10:39:15 +0000 +++ src/ssl/ServerBump.cc 2014-08-26 09:01:27 +0000 @@ -14,9 +14,11 @@ 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()); === modified file 'src/ssl/ServerBump.h' --- src/ssl/ServerBump.h 2013-06-06 13:53:16 +0000 +++ src/ssl/ServerBump.h 2014-07-28 18:48:30 +0000 @@ -20,7 +20,7 @@ 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 @@ -28,6 +28,9 @@ 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 === added file 'src/ssl/bio.cc' --- src/ssl/bio.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/bio.cc 2014-08-26 08:21:27 +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-26 08:21:27 +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-08-23 10:39:15 +0000 +++ src/ssl/support.cc 2014-08-26 09:01:27 +0000 @@ -40,11 +40,13 @@ #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" @@ -61,6 +63,12 @@ "none", "client-first", "server-first", + "peek", + "stare", + "bump", + "splice", + "terminate", + /*"err",*/ NULL }; @@ -997,6 +1005,143 @@ return sslContext; } +int Ssl::OpenSSLtoSquidSSLVersion(int sslVersion) +{ + 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; +} + + +#if OPENSSL_VERSION_NUMBER < 0x00909000L +SSL_METHOD * +#else +const SSL_METHOD * +#endif +Ssl::method(int version) +{ + switch (version) { + + 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."); + 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."); + return SSLv3_server_method(); + break; + + case 4: + debugs(83, 5, "Using TLSv1."); + 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."); + 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"); + 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."); + 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) { @@ -1013,55 +1158,8 @@ if (!certfile) certfile = keyfile; - switch (version) { - - case 2: -#ifndef OPENSSL_NO_SSL2 - debugs(83, 5, "Using SSLv2."); - method = 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."); - method = SSLv3_client_method(); - break; - - case 4: - debugs(83, 5, "Using TLSv1."); - method = 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."); - method = 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"); - method = 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."); - method = SSLv23_client_method(); - break; - } + if (!(method = Ssl::method(version))) + return NULL; sslContext = SSL_CTX_new(method); @@ -1452,8 +1550,8 @@ /// \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)); @@ -1500,6 +1598,49 @@ 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 @@ -1642,6 +1783,48 @@ 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); === modified file 'src/ssl/support.h' --- src/ssl/support.h 2014-08-23 10:39:15 +0000 +++ src/ssl/support.h 2014-08-26 09:01:27 +0000 @@ -83,6 +83,14 @@ 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 @@ -150,7 +158,9 @@ \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 @@ -227,6 +237,27 @@ /** \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); @@ -278,6 +309,16 @@ */ 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 === 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:46:52 +0000 @@ -8,3 +8,5 @@ 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-08-23 10:39:15 +0000 +++ src/tunnel.cc 2014-08-26 09:01:27 +0000 @@ -44,6 +44,7 @@ #include "comm/Write.h" #include "errorpage.h" #include "fde.h" +#include "globals.h" #include "FwdState.h" #include "http.h" #include "HttpRequest.h" @@ -55,7 +56,9 @@ #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 @@ -119,6 +122,11 @@ /// 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)); } @@ -264,6 +272,7 @@ http(), request(NULL), status_ptr(NULL), + logTag_ptr(NULL), connectRespBuf(NULL), connectReqWriting(false) { @@ -711,7 +720,8 @@ { 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 @@ -964,7 +974,7 @@ "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; } @@ -1094,3 +1104,82 @@ } #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