From: Christos Tsantilas Date: Tue, 20 Dec 2011 15:35:27 +0000 (+0200) Subject: Forward a StoreEntry holding the bumped secure connection establishment X-Git-Tag: BumpSslServerFirst.take01~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=061bbdecd2c1981f70aa8da4fb1a2e2eb3a0a1e5;p=thirdparty%2Fsquid.git Forward a StoreEntry holding the bumped secure connection establishment error page from the server-side code (which generates the error) to the client-side code (which delays the error until the first encrypted request comes). This allows Squid to display the error page to the user (using secure connection) when bumping intercepted SSL connections. The code still needs more polishing, including generating errors with host names and not IP addresses (when possible). The peeked server certificate is now stored in ConnStateData::bumpErrorEntry. This allows us to mimic the certificate of dropped server connections. Load signing certificate and key when initializing a tproxy-enabled https_port. Fixed debugging when opening a peeking connection. --- diff --git a/src/cache_cf.cc b/src/cache_cf.cc index ac9a8a7394..add7c03dd2 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -928,6 +928,9 @@ configDoConfigure(void) s->version, s->cipher, s->options, s->sslflags, s->clientca, s->cafile, s->capath, s->crlfile, s->dhfile, s->sslContextSessionId)); + + if (s->cert && s->key && s->sslBump) + Ssl::readCertChainAndPrivateKeyFromFiles(s->signingCert, s->signPkey, s->certsToChain, s->cert, s->key); } } diff --git a/src/client_side.cc b/src/client_side.cc index d1577c94c6..4dec7a8113 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -810,6 +810,11 @@ ConnStateData::~ConnStateData() if (bodyPipe != NULL) stopProducingFor(bodyPipe, false); + +#if USE_SSL + if (bumpErrorEntry) + bumpErrorEntry->unlock(); +#endif } /** @@ -2639,6 +2644,25 @@ clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *c conn->flags.readMore = false; } +#if USE_SSL + if (conn->switchedToHttps() && conn->bumpServerFirstErrorEntry() && + !Comm::IsConnOpen(conn->pinning.serverConnection)) { + //Failed? Here we should get the error from conn and send it to client + // The error stored in ConnStateData::bumpFirstEntry, replace the + // ClientHttpRequest store entry with this. + clientStreamNode *node = context->getClientReplyContext(); + clientReplyContext *repContext = dynamic_cast(node->data.getRaw()); + assert (repContext); + debugs(33, 5, "Connection first has failed for " << http->uri << ". Respond with an error"); + StoreEntry *e = conn->bumpServerFirstErrorEntry(); + Must(e && !e->isEmpty()); + repContext->setReplyToStoreEntry(e); + context->pullData(); + conn->flags.readMore = false; + goto finish; + } +#endif + /* Do we expect a request-body? */ expectBody = chunked || request->content_length > 0; if (!context->mayUseConnection() && expectBody) { @@ -3580,12 +3604,6 @@ ConnStateData::getSslContextStart() debugs(33, 5, HERE << "SSL certificate for " << host << " haven't found in cache"); } - Ssl::X509_Pointer serverCert; - if (Comm::IsConnOpen(pinning.serverConnection)) { - SSL *ssl = fd_table[pinning.serverConnection->fd].ssl; - serverCert.reset(SSL_get_peer_certificate(ssl)); - } - #if USE_SSL_CRTD debugs(33, 5, HERE << "Generating SSL certificate for " << host << " using ssl_crtd."); Ssl::CrtdMessage request_message; @@ -3594,8 +3612,8 @@ ConnStateData::getSslContextStart() map.insert(std::make_pair(Ssl::CrtdMessage::param_host, host)); std::string bufferToWrite; Ssl::writeCertAndPrivateKeyToMemory(port->signingCert, port->signPkey, bufferToWrite); - if (serverCert.get()) { - Ssl::appendCertToMemory(serverCert, bufferToWrite); + if (bumpServerCert.get()) { + Ssl::appendCertToMemory(bumpServerCert, bufferToWrite); debugs(33, 5, HERE << "Append Mimic Certificate to body request: " << bufferToWrite); } request_message.composeBody(map, bufferToWrite); @@ -3603,7 +3621,7 @@ ConnStateData::getSslContextStart() return; #else debugs(33, 5, HERE << "Generating SSL certificate for " << host); - dynCtx = Ssl::generateSslContext(host, serverCert, port->signingCert, port->signPkey); + dynCtx = Ssl::generateSslContext(host, bumpServerCert, port->signingCert, port->signPkey); getSslContextDone(dynCtx, true); return; #endif //USE_SSL_CRTD @@ -3675,9 +3693,12 @@ ConnStateData::switchToHttps(const char *host, const int port) const bool alwaysBumpServerFirst = true; if (alwaysBumpServerFirst) { Must(!httpsPeeker.set()); - httpsPeeker = AsyncJob::Start(new Ssl::ServerPeeker( - this, sslHostName.termedBuf(), port)); + httpsPeeker = new Ssl::ServerPeeker(this, sslHostName.termedBuf(), port); + bumpErrorEntry = httpsPeeker->storeEntry(); + Must(bumpErrorEntry); + bumpErrorEntry->lock(); // will call httpsPeeked() with certificate and connection, eventually + AsyncJob::Start(httpsPeeker.raw()); return; } @@ -3690,26 +3711,30 @@ ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) { Must(httpsPeeker.set()); - /* XXX: handle httpsPeeker errors instead of asserting there are none */ - assert(Comm::IsConnOpen(serverConnection)); - SSL *ssl = fd_table[serverConnection->fd].ssl; - assert(ssl); - Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl)); - assert(serverCert.get() != NULL); - - char name[256] = ""; // stores common name (CN) - // TODO: What if CN is a UTF8String? See X509_NAME_get_index_by_NID(3ssl). - const int nameLen = X509_NAME_get_text_by_NID( - X509_get_subject_name(serverCert.get()), - NID_commonName, name, sizeof(name)); - assert(0 < nameLen && nameLen < static_cast(sizeof(name))); - debugs(33, 5, HERE << "found HTTPS server " << name << " at bumped " << - *serverConnection); - sslHostName = name; - - pinConnection(serverConnection, NULL, NULL, false); - - debugs(33, 5, HERE << "bumped HTTPS server: " << sslHostName); + if (Comm::IsConnOpen(serverConnection)) { + SSL *ssl = fd_table[serverConnection->fd].ssl; + assert(ssl); + Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl)); + assert(serverCert.get() != NULL); + + char name[256] = ""; // stores common name (CN) + // TODO: What if CN is a UTF8String? See X509_NAME_get_index_by_NID(3ssl). + const int nameLen = X509_NAME_get_text_by_NID( + X509_get_subject_name(serverCert.get()), + NID_commonName, name, sizeof(name)); + assert(0 < nameLen && nameLen < static_cast(sizeof(name))); + debugs(33, 5, HERE << "found HTTPS server " << name << " at bumped " << + *serverConnection); + sslHostName = name; + + pinConnection(serverConnection, NULL, NULL, false); + + debugs(33, 5, HERE << "bumped HTTPS server: " << sslHostName); + } else + debugs(33, 5, HERE << "Error while bumped HTTPS server: " << sslHostName); + + if (httpsPeeker.valid()) + httpsPeeker->noteHttpsPeeked(serverConnection); httpsPeeker.clear(); getSslContextStart(); } @@ -4004,8 +4029,10 @@ CBDATA_CLASS_INIT(ConnStateData); ConnStateData::ConnStateData() : AsyncJob("ConnStateData"), - closing_(false), - switchedToHttps_(false) + closing_(false) +#if USE_SSL + ,switchedToHttps_(false) +#endif { pinning.pinned = false; pinning.auth = false; diff --git a/src/client_side.h b/src/client_side.h index 295bc4c9f8..72c6207908 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -44,6 +44,9 @@ #include "HttpParser.h" #include "RefCount.h" #include "StoreIOBuffer.h" +#if USE_SSL +#include "ssl/support.h" +#endif class ConnStateData; class ClientHttpRequest; @@ -160,7 +163,11 @@ private: class ConnectionDetail; - +#if USE_SSL +namespace Ssl { + class ServerPeeker; +} +#endif /** * Manages a connection to a client. * @@ -324,6 +331,9 @@ public: void switchToHttps(const char *host, const int port); bool switchedToHttps() const { return switchedToHttps_; } + /// Holds the squid error reply in the case of bump server first error + StoreEntry *bumpServerFirstErrorEntry() const {return bumpErrorEntry;} + void setBumpServerCert(X509 *serverCert) {bumpServerCert.reset(serverCert);} #else bool switchedToHttps() const { return false; } #endif @@ -351,7 +361,9 @@ private: String sslHostName; ///< Host name for SSL certificate generation /// a job that connects to the HTTPS server to get its SSL certificate - AsyncJob::Pointer httpsPeeker; + CbcPointer httpsPeeker; + StoreEntry *bumpErrorEntry; + Ssl::X509_Pointer bumpServerCert; #endif AsyncCall::Pointer reader; ///< set when we are reading diff --git a/src/client_side_reply.cc b/src/client_side_reply.cc index 0014fa07cd..0e3ff46bb9 100644 --- a/src/client_side_reply.cc +++ b/src/client_side_reply.cc @@ -128,6 +128,18 @@ clientReplyContext::setReplyToError( /* Now the caller reads to get this */ } +void clientReplyContext::setReplyToStoreEntry(StoreEntry *entry) +{ + sc = storeClientListAdd(entry, this); +#if USE_DELAY_POOLS + sc->setDelayId(DelayId::DelayClient(http)); +#endif + reqofs = 0; + reqsize = 0; + flags.storelogiccomplete = 1; + http->storeEntry(entry); +} + void clientReplyContext::removeStoreReference(store_client ** scp, StoreEntry ** ep) diff --git a/src/client_side_reply.h b/src/client_side_reply.h index c7e2b34be2..e92f41f183 100644 --- a/src/client_side_reply.h +++ b/src/client_side_reply.h @@ -71,6 +71,8 @@ public: void identifyFoundObject(StoreEntry *entry); int storeOKTransferDone() const; int storeNotOKTransferDone() const; + /// Replaces the store entry with the given and awaiting the client side to read it + void setReplyToStoreEntry(StoreEntry *entry); void setReplyToError(err_type, http_status, const HttpRequestMethod&, char const *, Ip::Address &, HttpRequest *, const char *, #if USE_AUTH AuthUserRequest::Pointer); diff --git a/src/forward.cc b/src/forward.cc index 449124a10e..23a21d6cc5 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -59,6 +59,7 @@ #if USE_SSL #include "ssl/support.h" #include "ssl/ErrorDetail.h" +#include "ssl/ServerPeeker.h" #endif static PSC fwdPeerSelectionCompleteWrapper; @@ -147,11 +148,11 @@ FwdState::selectPeerForIntercepted() p->peerType = PINNED; entry->ping_status = PING_DONE; /* Skip ICP */ } else { - debugs(17, 3, HERE << "opening a new conn: " << *p); p = new Comm::Connection(); p->peerType = ORIGINAL_DST; p->remote = clientConn->local; getOutgoingAddress(request, p); + debugs(17, 3, HERE << "opening a new conn: " << *p); } serverDestinations.push_back(p); @@ -336,6 +337,14 @@ FwdState::startConnectionOrFail() anErr->xerrno = errno; fail(anErr); } // else use actual error from last connection attempt +#if USE_SSL + if (request->protocol == AnyP::PROTO_SSL_PEEK && request->clientConnectionManager.valid()) { + errorAppendEntry(entry, err); // will free err + err = NULL; + CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData, + ConnStateData::httpsPeeked, Comm::ConnectionPointer(NULL)); + } +#endif self = NULL; // refcounted } } @@ -646,16 +655,22 @@ FwdState::negotiateSSL(int fd) // Copy errFromFailure to a new Ssl::ErrorDetail object anErr->detail = new Ssl::ErrorDetail(*errFromFailure); } else { - // clientCert can be be NULL - X509 *client_cert = SSL_get_peer_certificate(ssl); - anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, client_cert); - if (client_cert) - X509_free(client_cert); + // server_cert can be be NULL + X509 *server_cert = SSL_get_peer_certificate(ssl); + anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert); + X509_free(server_cert); } if (ssl_lib_error != SSL_ERROR_NONE) anErr->detail->setLibError(ssl_lib_error); + if (request->clientConnectionManager.valid()) { + // Get the server certificate from ErrorDetail object and store it + // to connection manager + X509 *x509 = anErr->detail->peerCert(); + request->clientConnectionManager->setBumpServerCert(X509_dup(x509)); + } + fail(anErr); if (serverConnection()->getPeer()) { @@ -666,6 +681,9 @@ FwdState::negotiateSSL(int fd) return; } } + + if (request->clientConnectionManager.valid()) + request->clientConnectionManager->setBumpServerCert(SSL_get_peer_certificate(ssl)); if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) { if (serverConnection()->getPeer()->sslSession) @@ -984,11 +1002,11 @@ FwdState::dispatch() #if USE_SSL if (request->protocol == AnyP::PROTO_SSL_PEEK) { CallJobHere1(17, 4, request->clientConnectionManager, ConnStateData, - ConnStateData::httpsPeeked, serverConnection()); + ConnStateData::httpsPeeked, serverConnection()); unregister(serverConn); // async call owns it now complete(); // destroys us return; - } + } #endif if (serverConnection()->getPeer() != NULL) { diff --git a/src/ssl/ErrorDetail.h b/src/ssl/ErrorDetail.h index 2e28962636..8938551d80 100644 --- a/src/ssl/ErrorDetail.h +++ b/src/ssl/ErrorDetail.h @@ -56,7 +56,8 @@ public: ssl_error_t errorNo() const {return error_no;} ///Sets the low-level error returned by OpenSSL ERR_get_error() void setLibError(unsigned long lib_err_no) {lib_error_no = lib_err_no;} - + ///The peer certificate + X509 *peerCert() { return peer_cert.get(); } private: typedef const char * (ErrorDetail::*fmt_action_t)() const; /** diff --git a/src/ssl/ServerPeeker.cc b/src/ssl/ServerPeeker.cc index 21c04c2d36..7003654405 100644 --- a/src/ssl/ServerPeeker.cc +++ b/src/ssl/ServerPeeker.cc @@ -21,8 +21,7 @@ Ssl::ServerPeeker::ServerPeeker(ConnStateData *anInitiator, AsyncJob("Ssl::ServerPeeker"), initiator(anInitiator), clientConnection(anInitiator->clientConnection), - request(new HttpRequest), - entry(NULL) + request(new HttpRequest) { debugs(33, 4, HERE << "will peek at " << host << ':' << port); @@ -30,20 +29,25 @@ Ssl::ServerPeeker::ServerPeeker(ConnStateData *anInitiator, request->port = port; request->protocol = AnyP::PROTO_SSL_PEEK; request->clientConnectionManager = initiator; + const char *uri = urlCanonical(request); + entry = storeCreateEntry(uri, uri, request->flags, request->method); +} + +Ssl::ServerPeeker::~ServerPeeker() +{ + if (entry) + entry->unlock(); } void Ssl::ServerPeeker::start() { - const char *uri = urlCanonical(request); - entry = storeCreateEntry(uri, uri, request->flags, request->method); - FwdState::fwdStart(clientConnection, entry, request); +} - // XXX: wait for FwdState to tell us the connection is ready - - // TODO: send our answer to the initiator - // CallJobHere(33, 4, initiator, ConnStateData, ConnStateData::httpsPeeked); +void Ssl::ServerPeeker::noteHttpsPeeked(Comm::ConnectionPointer &serverConnection) +{ + assert(initiator.raw()); initiator.clear(); // will trigger the end of the job } diff --git a/src/ssl/ServerPeeker.h b/src/ssl/ServerPeeker.h index e0e4620f9d..1e2849972a 100644 --- a/src/ssl/ServerPeeker.h +++ b/src/ssl/ServerPeeker.h @@ -32,10 +32,12 @@ public: explicit ServerPeeker(ConnStateData *anInitiator, const char *host, const int port); /* AsyncJob API */ - //virtual ~ServerPeeker(); + virtual ~ServerPeeker(); virtual void start(); virtual bool doneAll() const; virtual void swanSong(); + StoreEntry *storeEntry() {return entry;} + void noteHttpsPeeked(Comm::ConnectionPointer &serverConnection); private: /// connection manager waiting for peeked server info