SQUID_STATE_ROLLBACK(check_TXTDB)
])
+
+dnl Check if we can rewrite the hello message stored in SSL openSSL object
+dnl The tests are very basic, just check if the required SSL members exist
+dnl in 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 <openssl/ssl.h>
+ #include <openssl/err.h>
+ #include <assert.h>
+ ],
+ [
+ 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)
+]
+)
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])
# 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.
+ # step1: After getting TCP-level and HTTP CONNECT info.
+ # step2: After getting SSL Client Hello info.
+ # step3: After getting SSL Server Hello info.
ENDIF
acl aclname any-of acl1 acl2 ...
# match any one of the acls [fast or slow]
mimicked server certificate, with the client.
peek
- Receive client (step SslBump1) or server (step SslBump2)
+ Receive client (step step1) or server (step step2)
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)
+ Receive client (step step1) or server (step step2)
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:
+ Backward compatibility actions available at step step1:
client-first
Bump the connection. Establish a secure connection with the
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_CLIENT_SNI, "%%ssl::>sni");
DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_SUBJECT, "%%ssl::<cert_subject");
DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_ISSUER, "%%ssl::<cert_issuer");
#endif
Ssl::ClientBio *clnBio = static_cast<Ssl::ClientBio *>(b->ptr);
const Ssl::Bio::sslFeatures &features = clnBio->getFeatures();
if (features.sslVersion != -1) {
- SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion()));
-#ifdef TLSEXT_NAMETYPE_host_name
- if (!features.serverName.empty())
- SSL_set_tlsext_host_name(ssl, features.serverName.c_str());
-#endif
- if (!features.clientRequestedCiphers.empty())
- SSL_set_cipher_list(ssl, features.clientRequestedCiphers.c_str());
-#ifdef SSL_OP_NO_COMPRESSION /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */
- if (features.compressMethod == 0)
- SSL_set_options(ssl, SSL_OP_NO_COMPRESSION);
-#endif
+ features.applyToSSL(ssl);
// Should we allow it for all protocols?
if (features.sslVersion >= 3) {
b = SSL_get_rbio(ssl);
if (peekMode < Ssl::bumpSplice)
peekMode = Ssl::bumpBump;
-#if 1 || SSL_BUMP_FORCE_PEEK_OR_SPLICE
if (peekMode == Ssl::bumpSplice && !srvBio->canSplice())
peekMode = Ssl::bumpPeek;
else if (peekMode == Ssl::bumpBump && !srvBio->canBump())
peekMode = Ssl::bumpSplice;
-#endif
if (peekMode == Ssl::bumpTerminate || peekMode == Ssl::bumpErr) {
comm_close(serverConn->fd);
// 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, DBG_IMPORTANT, "fwdNegotiateSSL: Error but, hold write on SSL connection on FD " << fd);
+ debugs(81, DBG_IMPORTANT, "fwdNegotiateSSL: Error (" << ERR_error_string(ssl_lib_error, NULL) << ") but, hold write on SSL connection on FD " << fd);
checkForPeekAndSplice(false, Ssl::bumpNone);
return;
}
int
Ssl::ClientBio::read(char *buf, int size, BIO *table)
{
- if (headerState < 2) {
+ if (helloState < atHelloReceived) {
if (rbuf.isNull())
rbuf.init(1024, 16384);
debugs(83, 7, "rbuf size: " << rbuf.contentSize());
}
- if (headerState == 0) {
+ if (helloState == atHelloNone) {
const unsigned char *head = (const unsigned char *)rbuf.content();
const char *s = objToString(head, rbuf.contentSize());
if (head[0] == 0x16) {
debugs(83, 7, "SSL version 3 handshake message");
- headerBytes = (head[3] << 8) + head[4];
- debugs(83, 7, "SSL Header Size: " << headerBytes);
- headerBytes +=5;
+ helloSize = (head[3] << 8) + head[4];
+ debugs(83, 7, "SSL Header Size: " << helloSize);
+ helloSize +=5;
#ifdef DO_SSLV23
} else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) {
debugs(83, 7, "SSL version 2 handshake message with v3 support");
- headerBytes = head[1];
- headerBytes +=5;
+ helloSize = head[1];
+ helloSize +=5;
#endif
}else {
debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)");
return -1;
}
- headerState = 1; //Next state
+ helloState = atHelloStarted; //Next state
}
- if (headerState == 1) {
+ 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 (headerBytes > rbuf.contentSize()) {
+ if (helloSize > rbuf.contentSize()) {
BIO_set_retry_read(table);
return -1;
}
features.get((const unsigned char *)rbuf.content());
- headerState = 2;
+ helloState = atHelloReceived;
}
if (holdRead_) {
return -1;
}
- if (headerState >=2) {
+ if (helloState == atHelloReceived) {
if (rbuf.hasContent()) {
int bytes = (size <= rbuf.contentSize() ? size : rbuf.contentSize());
memcpy(buf, rbuf.content(), bytes);
return bytes;
}
-bool
-adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features, bool force)
+
+// This function makes the required checks to examine if the client hello message
+// can be 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)
{
- bool fail = false;
+#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!");
- fail = true;
+ return false;
}
- //Check ciphers list
+ // Check ciphers list
size_t token = 0;
size_t end = 0;
while (token != std::string::npos) {
}
if (!found) {
debugs(83, 5, "Client Hello Data supports cipher '"<< cipher <<"' but we do not support it!");
- fail = true;
+ return false;
}
}
#if !defined(SSL_TLSEXT_HB_ENABLED)
if (features.doHeartBeats) {
debugs(83, 5, "Client Hello Data supports HeartBeats but we do not support!");
- fail = true;
+ return false;
}
#endif
#ifdef TLSEXT_TYPE_use_srtp
TLSEXT_TYPE_use_srtp,
#endif
-#if 1 //Allow 13172 Firefox supported extension for testing purposes
+#if 0 //Allow 13172 Firefox supported extension for testing purposes
13172,
#endif
-1
}
if (!found) {
debugs(83, 5, "Extension " << *it << " does not supported!");
- fail = true ;
+ return false;
}
}
- if (fail && !force)
+ SSL3_BUFFER *wb=&(ssl->s3->wbuf);
+ if (wb->len < (size_t)features.helloMessage.contentSize())
return false;
- if (fail) {
- debugs(83, 5, "Hello Data are OK but can not be bumped any more!");
- }
-
- debugs(83, 5, "Hello Data will be mimicked!");
+ 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);
-
- SSL3_BUFFER *wb=&(ssl->s3->wbuf);
- assert(wb->len > (size_t)features.helloMessage.contentSize());
memcpy(wb->buf, features.helloMessage.content(), features.helloMessage.contentSize());
wb->left = features.helloMessage.contentSize();
ssl->init_num = mainHelloSize;
ssl->s3->wpend_ret = mainHelloSize;
ssl->s3->wpend_tot = mainHelloSize;
- return !fail;
+ return true;
+#else
+ return false;
+#endif
}
int
assert(!helloMsg.hasContent());
SSL *ssl = fd_table[fd_].ssl;
- if (featuresSet && ssl && ssl->s3) {
+ if (featuresSet && ssl) {
if (bumpMode_ == Ssl::bumpPeek) {
- if (adjustSSL(ssl, clientFeatures, true))
+ if (adjustSSL(ssl, clientFeatures))
allowBump = true;
allowSplice = true;
helloMsg.append(clientFeatures.helloMessage.content(), clientFeatures.helloMessage.contentSize());
debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for peek mode");
} else { /*Ssl::bumpStare*/
allowBump = true;
- if (adjustSSL(ssl, clientFeatures, false)) {
+ if (adjustSSL(ssl, clientFeatures)) {
allowSplice = true;
helloMsg.append(clientFeatures.helloMessage.content(), clientFeatures.helloMessage.contentSize());
debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted for stare mode");
// Sending hello message complete. Do not send more data for now...
holdWrite_ = true;
- // The size should be less than the size of the hello message
- //assert(size >= helloMsgSize);
- return helloMsgSize;
+ // spoof openSSL that we write what it ask us to write
+ return size;
} else
return Ssl::Bio::write(buf, size, table);
}
#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()));
+#ifdef TLSEXT_NAMETYPE_host_name
+ if (!serverName.empty())
+ SSL_set_tlsext_host_name(ssl, serverName.c_str());
+#endif
+ if (!clientRequestedCiphers.empty())
+ SSL_set_cipher_list(ssl, clientRequestedCiphers.c_str());
+#ifdef 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
{
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
};
/// 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:
- explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), headerState(0), headerBytes(0) {}
+ /// 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
Bio::sslFeatures features;
bool holdRead_; ///< The read hold state of the bio.
bool holdWrite_; ///< The write hold state of the bio.
- int headerState;
- int headerBytes;
+ 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) {}
/// 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
MemBuf helloMsg; ///< Used to buffer output data.
int helloMsgSize;
bool helloBuild; ///< True if the client hello message sent to the server
- bool allowSplice;
- bool allowBump;
+ 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_;
+ bool record_; ///< If true the input data recorded to rbuf for internal use
Ssl::BumpMode bumpMode_;
};
http(),
request(NULL),
status_ptr(NULL),
+ logTag_ptr(NULL),
connectRespBuf(NULL),
connectReqWriting(false)
{
{
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
++statCounter.server.other.requests;
tunnelState = new TunnelStateData;
-#if USE_DELAY_POOLS
- //tunnelState->server.setDelayId(DelayId::DelayClient(http));
-#endif
tunnelState->url = xstrdup(url);
tunnelState->request = request;
tunnelState->server.size_ptr = NULL;//????
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;
+
+#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);
fd_table[clientConn->fd].read_method = &default_read_method;
fd_table[clientConn->fd].write_method = &default_write_method;
-//Server connection
-#if USE_DELAY_POOLS
- /* no point using the delayIsNoDelay stuff since tunnel is nice and simple */
-// if (conn->getPeer() && conn->getPeer()->options.no_delay)
-// tunnelState->server.setDelayId(DelayId());
-#endif
-
tunnelState->request->hier.note(srvConn, tunnelState->getHost());
tunnelState->server.conn = srvConn;
fd_table[srvConn->fd].read_method = &default_read_method;
fd_table[srvConn->fd].write_method = &default_write_method;
- ConnStateData *conn;
- if ((conn = request->pinnedConnection()) && conn->serverBump() && conn->serverBump()->step == Ssl::bumpStep2) {
- SSL *ssl = fd_table[clientConn->fd].ssl;
- assert(ssl);
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ClientBio *srvBio = static_cast<Ssl::ClientBio *>(b->ptr);
- const MemBuf &buf = srvBio->rBufData();
+ SSL *ssl = fd_table[srvConn->fd].ssl;
+ assert(ssl);
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ const MemBuf &buf = srvBio->rBufData();
- AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
- CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
- Comm::Write(tunnelState->server.conn, buf.content(), buf.contentSize(), call, NULL);
- } else {
- SSL *ssl = fd_table[srvConn->fd].ssl;
- assert(ssl);
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
- const MemBuf &buf = srvBio->rBufData();
-
- AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
- CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
- Comm::Write(tunnelState->client.conn, buf.content(), buf.contentSize(), call, NULL);
- }
+ AsyncCall::Pointer call = commCbCall(5,5, "tunnelConnectedWriteDone",
+ CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
+ Comm::Write(tunnelState->client.conn, buf.content(), buf.contentSize(), call, NULL);
}