#include "globals.h"
#include "HttpRequest.h"
#include "neighbors.h"
+#include "SquidConfig.h"
#include "ssl/bio.h"
#include "ssl/cert_validate_message.h"
#include "ssl/Config.h"
if (features.sslVersion >= 3) {
b = SSL_get_rbio(ssl);
Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
- srvBio->setClientRandom(features.client_random);
+ srvBio->setClientFeatures(features);
srvBio->recordInput(true);
}
}
}
void switchToTunnel(HttpRequest *request, int *status_ptr, Comm::ConnectionPointer & clientConn, Comm::ConnectionPointer &srvConn);
+
void
-Ssl::PeerConnector::checkForPeekAndSplice()
+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;
BIO *b = SSL_get_rbio(ssl);
Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
debugs(83,5, "Will check for peek and splice on fd " << serverConn->fd);
- const bool splice = true;
+ bool splice;
+ if (!srvBio->canSplice())
+ splice = false;
+ else if (!checkDone) {
+ ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(
+ ::Config.accessList.ssl_bump_peeked,
+ request.getRaw(), NULL);
+ acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSplice, this);
+ return false;
+ } else
+ splice = (peekMode == Ssl::bumpSplice);
+
if (!splice) {
//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;
}
}
case SSL_ERROR_WANT_WRITE:
if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice && srvBio->holdWrite()) {
debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
- checkForPeekAndSplice();
+ checkForPeekAndSplice(false, Ssl::bumpNone);
return;
}
Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
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::bumpPeekAndSplice && srvBio->holdWrite()) {
+ debugs(81, DBG_IMPORTANT, "fwdNegotiateSSL: Error 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;
if (headerState < 2) {
if (rbuf.isNull())
- rbuf.init(1024, 4096);
+ rbuf.init(1024, 16384);
size = rbuf.spaceSize() > size ? size : rbuf.spaceSize();
debugs(83, 7, "SSL version 3 handshake message");
headerBytes = (head[3] << 8) + head[4];
debugs(83, 7, "SSL Header Size: " << headerBytes);
+ headerBytes +=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;
#endif
}else {
debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)");
}
void
-Ssl::ServerBio::setClientRandom(const unsigned char *r)
+Ssl::ServerBio::setClientFeatures(const Ssl::Bio::sslFeatures &features)
{
- memcpy(clientRandom, r, SSL3_RANDOM_SIZE);
- randomSet = true;
+ 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.init(features.helloMessage.contentSize(), features.helloMessage.contentSize());
+ clientFeatures.helloMessage.append(features.helloMessage.content(), features.helloMessage.contentSize());
+ clientFeatures.doHeartBeats = features.doHeartBeats;
+ clientFeatures.extensions = features.extensions;
+ featuresSet = true;
};
int
if (bytes > 0 && record_) {
if (rbuf.isNull())
- rbuf.init(1024, 8196);
+ 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;
}
+bool
+adjustSSL(SSL *ssl, Ssl::Bio::sslFeatures &features)
+{
+ // 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<int>::iterator it = features.extensions.begin(); it != features.extensions.end(); ++it) {
+ static int supportedExtensions[] = {
+ TLSEXT_TYPE_server_name,
+#ifdef TLSEXT_TYPE_opaque_prf_input
+ TLSEXT_TYPE_opaque_prf_input,
+#endif
+#ifdef TLSEXT_TYPE_heartbeat
+ TLSEXT_TYPE_heartbeat,
+#endif
+#ifdef TLSEXT_TYPE_renegotiate
+ TLSEXT_TYPE_renegotiate,
+#endif
+#ifdef TLSEXT_TYPE_ec_point_formats
+ TLSEXT_TYPE_ec_point_formats,
+#endif
+#ifdef TLSEXT_TYPE_elliptic_curves
+ TLSEXT_TYPE_elliptic_curves,
+#endif
+#ifdef TLSEXT_TYPE_session_ticket
+ TLSEXT_TYPE_session_ticket,
+#endif
+#ifdef TLSEXT_TYPE_status_request
+ TLSEXT_TYPE_status_request,
+#endif
+#ifdef TLSEXT_TYPE_use_srtp
+ TLSEXT_TYPE_use_srtp,
+#endif
+#if 1 //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;
+ }
+ }
+
+
+ debugs(83, 5, "Hello Data are OK and can be mimicked!");
+
+ //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();
+
+ size_t mainHelloSize = features.helloMessage.contentSize() - 5;
+ const char *mainHello = features.helloMessage.content() + 5;
+ assert(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;
+}
+
int
Ssl::ServerBio::write(const char *buf, int size, BIO *table)
{
if (holdWrite_) {
- debugs(83, 7, "Hold write, for SSL connection on " << fd_);
+ 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) {
+ if (helloMsg.isNull())
+ helloMsg.init(1024, 16384);
+
if (
buf[1] >= 3 //it is an SSL Version3 message
&& buf[0] == 0x16 // and it is a Handshake/Hello message
) {
- if (helloMsg.isNull())
- helloMsg.init(1024, 4096);
//Hello message is the first message we write to server
assert(!helloMsg.hasContent());
SSL *ssl = fd_table[fd_].ssl;
- if (randomSet && ssl && ssl->s3) {
- assert(size > 11 + SSL3_RANDOM_SIZE);
- helloMsg.append(buf, 11);
- //The random number is stored in the 11 position of the
- // message we are going to sent
- helloMsg.append((char *)clientRandom, SSL3_RANDOM_SIZE);
- size_t len = size - 11 - SSL3_RANDOM_SIZE;
- helloMsg.append(buf + 11 + SSL3_RANDOM_SIZE, len);
-
- // We need to fix the random in SSL struct:
- memcpy(ssl->s3->client_random, clientRandom, SSL3_RANDOM_SIZE);
- // We also need to fix the raw message in SSL struct
- // stored in SSL->init_buf. Looks that it is used to get
- // digest of the previous sent SSL message, to compute keys
- // for encryption/decryption:
- memcpy(ssl->init_buf->data + 6, clientRandom, SSL3_RANDOM_SIZE);
-
- debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted");
+ if (featuresSet && ssl && ssl->s3) {
+ 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");
+ }
}
}
+ // If we do not build any hello message, copy the current
+ if (!helloMsg.hasContent())
+ helloMsg.append(buf, size);
+
helloBuild = true;
helloMsgSize = helloMsg.contentSize();
+
+ if (allowSplice) {
+ // Do not write yet.....
+ BIO_set_retry_write(table);
+ return -1;
+ }
}
if (helloMsg.hasContent()) {
}
// Sending hello message complete. Do not send more data for now...
- holdWrite_ = true;
+ holdWrite_ = true;
+
// The size should be less than the size of the hello message
- assert(size >= helloMsgSize);
+ //assert(size >= helloMsgSize);
return helloMsgSize;
} else
return Ssl::Bio::write(buf, size, table);
}
}
-Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1)
+Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1), unknownCiphers(false), doHeartBeats(true)
{
memset(client_random, 0, SSL3_RANDOM_SIZE);
}
// 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.init(helloSize, helloSize);
+ 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*/) {
if(!clientRequestedCiphers.empty())
clientRequestedCiphers.append(":");
clientRequestedCiphers.append(c->name);
- }
+ } else
+ unknownCiphers = true;
}
}
debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers);
compressMethod = 0;
debugs(83, 7, "SSL compression methods number: " << (int)compression[0]);
- const unsigned char *extensions = compression + 1 + (int)compression[0];
- if (extensions < hello + helloSize) {
- int extensionsLen = (extensions[0] << 8) | extensions[1];
- const unsigned char *ext = extensions + 2;
- while (ext < extensions+extensionsLen){
+ 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];
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;
}
}
// The following hello message size exist in 2nd byte
int helloSize = hello[1];
helloSize += 2; //Include the 2 header bytes.
+ helloMessage.init(helloSize, helloSize);
+ helloMessage.append((char *)hello, helloSize);
//Ciphers list. It is stored after the Session ID.
#include "MemBuf.h"
#include <iosfwd>
+#include <list>
#if HAVE_OPENSSL_BIO_H
#include <openssl/bio.h>
#endif
int compressMethod; ///< The requested/used compressed method
std::string 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<int> extensions;
+ MemBuf helloMessage;
};
explicit Bio(const int anFd);
~Bio();
/// BIO node to handle socket IO for squid server side
class ServerBio: public Bio {
public:
- explicit ServerBio(const int anFd): Bio(anFd), randomSet(false), helloMsgSize(0), helloBuild(false), holdWrite_(false), record_(false) {}
+ explicit ServerBio(const int anFd): Bio(anFd), featuresSet(false), helloMsgSize(0), helloBuild(false), allowSplice(false), holdWrite_(false), record_(false) {}
/// The ServerBio version of the Ssl::Bio::stateChanged method
virtual void stateChanged(const SSL *ssl, int where, int ret);
/// The ServerBio version of the Ssl::Bio::write method
/// Flushes any buffered data
virtual void flush(BIO *table);
/// Sets the random number to use in client SSL HELLO message
- void setClientRandom(const unsigned char *r);
+ void setClientFeatures(const sslFeatures &features);
bool holdWrite() const {return holdWrite_;}
void holdWrite(bool h) {holdWrite_ = h;}
void recordInput(bool r) {record_ = r;}
const MemBuf &rBufData() {return rbuf;}
+ bool canSplice() {return allowSplice;}
private:
/// A random number to use as "client random" in client hello message
- unsigned char clientRandom[SSL3_RANDOM_SIZE];
- bool randomSet; ///< True if the clientRandom member is set and can be used
+ sslFeatures clientFeatures;
+ bool featuresSet; ///< True if the clientFeatures member is set and can be used
MemBuf helloMsg; ///< Used to buffer output data.
int helloMsgSize;
bool helloBuild; ///< True if the client hello message sent to the server
+ bool allowSplice;
bool holdWrite_; ///< The write hold state of the bio.
bool record_;
MemBuf rbuf; ///< Used to buffer input data.