From: Christos Tsantilas Date: Fri, 2 May 2014 08:34:46 +0000 (+0300) Subject: merge from trunk X-Git-Tag: SQUID_3_5_0_1~89^2~21 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=31855516f574fe5bea7e2f9b33147adbaf854c5a;p=thirdparty%2Fsquid.git merge from trunk --- 31855516f574fe5bea7e2f9b33147adbaf854c5a diff --cc configure.ac index 5a0afb5b37,46b76499af..22b6f9edbb --- a/configure.ac +++ b/configure.ac @@@ -2366,16 -2251,7 +2251,13 @@@ AC_CHECK_HEADERS( netinet/in.h \ netinet/in_systm.h \ netinet/ip_fil_compat.h \ + netinet/tcp.h \ + openssl/bio.h \ + openssl/err.h \ + openssl/md5.h \ + openssl/opensslv.h \ + openssl/ssl.h \ + openssl/x509v3.h \ - netinet/tcp.h \ - openssl/engine.h \ - openssl/txt_db.h \ - ostream \ paths.h \ poll.h \ pwd.h \ diff --cc src/cache_cf.cc index a7420ffb6c,3c21bacbc8..6ca7b61248 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@@ -4552,20 -4660,16 +4660,19 @@@ static void parse_sslproxy_ssl_bump(acl sslBumpCfgRr::lastDeprecatedRule = Ssl::bumpEnd; } - acl_access *A = new acl_access; - A->allow = allow_t(ACCESS_ALLOWED); + allow_t action = allow_t(ACCESS_ALLOWED); if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpClientFirst]) == 0) { - A->allow.kind = Ssl::bumpClientFirst; + action.kind = Ssl::bumpClientFirst; bumpCfgStyleNow = bcsNew; } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) { - A->allow.kind = Ssl::bumpServerFirst; + action.kind = Ssl::bumpServerFirst; bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpPeekAndSplice]) == 0) { - A->allow.kind = Ssl::bumpPeekAndSplice; ++ action.kind = Ssl::bumpPeekAndSplice; + bumpCfgStyleNow = bcsNew; } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpNone]) == 0) { - A->allow.kind = Ssl::bumpNone; + action.kind = Ssl::bumpNone; bumpCfgStyleNow = bcsNew; } else if (strcmp(bm, "allow") == 0) { debugs(3, DBG_CRITICAL, "SECURITY NOTICE: auto-converting deprecated " diff --cc src/client_side.cc index 70379490ca,b42dc70ed5..4831e42b67 --- a/src/client_side.cc +++ b/src/client_side.cc @@@ -136,29 -135,22 +135,23 @@@ #if USE_DELAY_POOLS #include "ClientInfo.h" #endif - #if USE_SSL + #if USE_OPENSSL +#include "ssl/bio.h" - #include "ssl/ProxyCerts.h" #include "ssl/context_storage.h" + #include "ssl/gadgets.h" #include "ssl/helper.h" + #include "ssl/ProxyCerts.h" #include "ssl/ServerBump.h" #include "ssl/support.h" - #include "ssl/gadgets.h" #endif #if USE_SSL_CRTD - #include "ssl/crtd_message.h" #include "ssl/certificate_db.h" + #include "ssl/crtd_message.h" #endif - #if HAVE_LIMITS_H - #include - #endif - #if HAVE_MATH_H - #include - #endif - #if HAVE_LIMITS + #include + #include #include - #endif #if LINGERING_CLOSE #define comm_close comm_lingering_close @@@ -3833,27 -3832,25 +3841,28 @@@ ConnStateData::getSslContextStart( Ssl::CertificateProperties certProperties; buildSslCertGenerationParams(certProperties); sslBumpCertKey = certProperties.dbKey().c_str(); - assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); + 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::bumpPeekAndSplice)) { + debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache"); - Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); ++ Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); + SSL_CTX * dynCtx = NULL; - Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf()); ++ 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; + } else { + debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); - ssl_ctx_cache.del(sslBumpCertKey.termedBuf()); ++ 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, HERE << "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 @@@ -3954,87 -3954,14 +3973,87 @@@ ConnStateData::switchToHttps(HttpReques sslServerBump = new Ssl::ServerBump(request); // will call httpsPeeked() with certificate and connection, eventually - FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request); + FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw()); return; } + else if (bumpServerMode == Ssl::bumpPeekAndSplice) { + request->flags.sslPeek = true; + sslServerBump = new Ssl::ServerBump(request, NULL, Ssl::bumpPeekAndSplice); + 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, 2, "Start peek and splice on" << 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()) { + debugs(83, 2, "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 +ConnStateData::startPeekAndSpliceDone() +{ - FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request); ++ 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, HERE << "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) { diff --cc src/client_side.h index c5a0099234,7ec69f182b..a7511d82dd --- a/src/client_side.h +++ b/src/client_side.h @@@ -331,15 -331,10 +331,18 @@@ public /// the client-side-detected error response instead of getting stuck. void quitAfterError(HttpRequest *request); // meant to be private - #if USE_SSL + /// The caller assumes responsibility for connection closure detection. + void stopPinnedConnectionMonitoring(); + + #if USE_OPENSSL + /// Initializes and starts a peek-and-splice negotiation with the SSL client + void startPeekAndSplice(); + /// Called when the initialization of peek-and-splice negotiation finidhed + void startPeekAndSpliceDone(); + /// Called when a peek-and-splice step finished. For example after + /// server-side SSL certificates received and client-side SSL certificates + /// generated + void doPeekAndSpliceStep(); /// called by FwdState when it is done bumping the server void httpsPeeked(Comm::ConnectionPointer serverConnection); diff --cc src/ssl/PeerConnector.cc index 0000000000,d6c32347a2..0682abdce0 mode 000000,100644..100644 --- a/src/ssl/PeerConnector.cc +++ b/src/ssl/PeerConnector.cc @@@ -1,0 -1,545 +1,586 @@@ + /* + * DEBUG: section 17 Request Forwarding + * + */ + + #include "squid.h" + #include "acl/FilledChecklist.h" + #include "base/AsyncCbdataCalls.h" + #include "CachePeer.h" + #include "client_side.h" + #include "comm/Loops.h" + #include "errorpage.h" + #include "fde.h" + #include "globals.h" + #include "HttpRequest.h" + #include "neighbors.h" ++#include "ssl/bio.h" + #include "ssl/cert_validate_message.h" + #include "ssl/Config.h" + #include "ssl/ErrorDetail.h" + #include "ssl/helper.h" + #include "ssl/PeerConnector.h" + #include "ssl/ServerBump.h" + #include "ssl/support.h" + #include "SquidConfig.h" + + CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector); + + Ssl::PeerConnector::PeerConnector( + HttpRequestPointer &aRequest, + const Comm::ConnectionPointer &aServerConn, + AsyncCall::Pointer &aCallback): + AsyncJob("Ssl::PeerConnector"), + request(aRequest), + serverConn(aServerConn), + callback(aCallback) + { + // if this throws, the caller's cb dialer is not our CbDialer + Must(dynamic_cast(callback->getDialer())); + } + + Ssl::PeerConnector::~PeerConnector() + { + debugs(83, 5, "Peer connector " << this << " gone"); + } + + bool Ssl::PeerConnector::doneAll() const + { + return (!callback || callback->canceled()) && AsyncJob::doneAll(); + } + + /// Preps connection and SSL state. Calls negotiate(). + void + Ssl::PeerConnector::start() + { + AsyncJob::start(); + + if (prepareSocket()) { + initializeSsl(); + negotiateSsl(); + } + } + + void + Ssl::PeerConnector::commCloseHandler(const CommCloseCbParams ¶ms) + { + debugs(83, 5, "FD " << params.fd << ", Ssl::PeerConnector=" << params.data); + connectionClosed("Ssl::PeerConnector::commCloseHandler"); + } + + void + Ssl::PeerConnector::connectionClosed(const char *reason) + { + mustStop(reason); + callback = NULL; + } + + bool + Ssl::PeerConnector::prepareSocket() + { + const int fd = serverConnection()->fd; + if (!Comm::IsConnOpen(serverConn) || fd_table[serverConn->fd].closing()) { + connectionClosed("Ssl::PeerConnector::prepareSocket"); + return false; + } + + // watch for external connection closures + typedef CommCbMemFunT Dialer; + closeHandler = JobCallback(9, 5, Dialer, this, Ssl::PeerConnector::commCloseHandler); + comm_add_close_handler(fd, closeHandler); + return true; + } + + void + Ssl::PeerConnector::initializeSsl() + { - SSL *ssl; + SSL_CTX *sslContext = NULL; + const CachePeer *peer = serverConnection()->getPeer(); + const int fd = serverConnection()->fd; + + if (peer) { + assert(peer->use_ssl); + sslContext = peer->sslContext; + } else { + sslContext = ::Config.ssl_client.sslContext; + } + + assert(sslContext); + - if ((ssl = SSL_new(sslContext)) == NULL) { ++ SSL *ssl = Ssl::CreateClient(sslContext, fd, "server https start"); ++ if (!ssl) { + ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw()); + anErr->xerrno = errno; + debugs(83, DBG_IMPORTANT, "Error allocating SSL handle: " << ERR_error_string(ERR_get_error(), NULL)); + bail(anErr); + return; + } + - SSL_set_fd(ssl, fd); - + if (peer) { + if (peer->ssldomain) + SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain); + + #if NOT_YET + + else if (peer->name) + SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name); + + #endif + + else + SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host); + + if (peer->sslSession) + SSL_set_session(ssl, peer->sslSession); + ++ } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice) { ++ 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) { ++ SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion())); ++ if (!features.serverName.empty()) ++ SSL_set_tlsext_host_name(ssl, features.serverName.c_str()); ++ 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 ++ if (features.sslVersion >= 3) { ++ b = SSL_get_rbio(ssl); ++ Ssl::ServerBio *srvBio = static_cast(b->ptr); ++ srvBio->setClientRandom(features.client_random); ++ srvBio->recordInput(true); ++ } ++ } + } else { + // While we are peeking at the certificate, we may not know the server + // name that the client will request (after interception or CONNECT) + // unless it was the CONNECT request with a user-typed address. + const char *hostname = request->GetHost(); + const bool hostnameIsIp = request->GetHostIsNumeric(); + const bool isConnectRequest = request->clientConnectionManager.valid() && + !request->clientConnectionManager->port->flags.isIntercepted(); + if (!request->flags.sslPeek || isConnectRequest) + SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname); + + // Use SNI TLS extension only when we connect directly + // to the origin server and we know the server host name. + if (!hostnameIsIp) + Ssl::setClientSNI(ssl, hostname); + } + + // If CertValidation Helper used do not lookup checklist for errors, + // but keep a list of errors to send it to CertValidator + if (!Ssl::TheConfig.ssl_crt_validator) { + // Create the ACL check list now, while we have access to more info. + // The list is used in ssl_verify_cb() and is freed in ssl_free(). + if (acl_access *acl = ::Config.ssl_client.cert_error) { + ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); + // check->fd(fd); XXX: need client FD here + SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check); + } + } + + // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE + X509 *peeked_cert; + if (request->clientConnectionManager.valid() && + request->clientConnectionManager->serverBump() && + (peeked_cert = request->clientConnectionManager->serverBump()->serverCert.get())) { + CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509); + SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert); + } - - fd_table[fd].ssl = ssl; - fd_table[fd].read_method = &ssl_read_method; - fd_table[fd].write_method = &ssl_write_method; + } + + void + Ssl::PeerConnector::negotiateSsl() + { + if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing()) + return; + + const int fd = serverConnection()->fd; + SSL *ssl = fd_table[fd].ssl; + const int result = SSL_connect(ssl); + if (result <= 0) { + handleNegotiateError(result); + return; // we might be gone by now + } + + if (request->clientConnectionManager.valid()) { + // remember the server certificate from the ErrorDetail object + if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { + serverBump->serverCert.reset(SSL_get_peer_certificate(ssl)); + + // remember validation errors, if any + if (Ssl::CertErrors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) + serverBump->sslErrors = cbdataReference(errs); + } + } + + if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) { + if (serverConnection()->getPeer()->sslSession) + SSL_SESSION_free(serverConnection()->getPeer()->sslSession); + + serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl); + } + + if (Ssl::TheConfig.ssl_crt_validator) { + Ssl::CertValidationRequest validationRequest; + // WARNING: Currently we do not use any locking for any of the + // members of the Ssl::CertValidationRequest class. In this code the + // Ssl::CertValidationRequest object used only to pass data to + // Ssl::CertValidationHelper::submit method. + validationRequest.ssl = ssl; + validationRequest.domainName = request->GetHost(); + if (Ssl::CertErrors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) + // validationRequest disappears on return so no need to cbdataReference + validationRequest.errors = errs; + else + validationRequest.errors = NULL; + try { + debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd."); + Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, sslCrtvdHandleReplyWrapper, this); + return; + } catch (const std::exception &e) { + debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " << + "request for " << validationRequest.domainName << + " certificate: " << e.what() << "; will now block to " << + "validate that certificate."); + // fall through to do blocking in-process generation. + ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); + bail(anErr); + if (serverConnection()->getPeer()) { + peerConnectFailed(serverConnection()->getPeer()); + } + serverConn->close(); + return; + } + } + + callBack(); + } + ++void ++Ssl::PeerConnector::checkForPeekAndSplice() ++{ ++ SSL *ssl = fd_table[serverConn->fd].ssl; ++ 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); ++ const bool splice = false; ++ 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); ++ } ++} ++ + void + Ssl::PeerConnector::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse) + { + Ssl::PeerConnector *connector = (Ssl::PeerConnector *)(data); + connector->sslCrtvdHandleReply(validationResponse); + } + + void + Ssl::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse const &validationResponse) + { + Ssl::CertErrors *errs = NULL; + Ssl::ErrorDetail *errDetails = NULL; + bool validatorFailed = false; + if (!Comm::IsConnOpen(serverConnection())) { + return; + } + + debugs(83,5, request->GetHost() << " cert validation result: " << validationResponse.resultCode); + + if (validationResponse.resultCode == HelperReply::Error) + errs = sslCrtvdCheckForErrors(validationResponse, errDetails); + else if (validationResponse.resultCode != HelperReply::Okay) + validatorFailed = true; + + if (!errDetails && !validatorFailed) { + callBack(); + return; + } + + ErrorState *anErr = NULL; + if (validatorFailed) { + anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); + } else { + + // Check the list error with + if (errDetails && request->clientConnectionManager.valid()) { + // remember the server certificate from the ErrorDetail object + if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { + // remember validation errors, if any + if (errs) { + if (serverBump->sslErrors) + cbdataReferenceDone(serverBump->sslErrors); + serverBump->sslErrors = cbdataReference(errs); + } + } + } + + anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw()); + anErr->detail = errDetails; + /*anErr->xerrno= Should preserved*/ + } + + bail(anErr); + if (serverConnection()->getPeer()) { + peerConnectFailed(serverConnection()->getPeer()); + } + serverConn->close(); + return; + } + + /// Checks errors in the cert. validator response against sslproxy_cert_error. + /// The first honored error, if any, is returned via errDetails parameter. + /// The method returns all seen errors except SSL_ERROR_NONE as Ssl::CertErrors. + Ssl::CertErrors * + Ssl::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails) + { + Ssl::CertErrors *errs = NULL; + + ACLFilledChecklist *check = NULL; + if (acl_access *acl = ::Config.ssl_client.cert_error) + check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); + + SSL *ssl = fd_table[serverConnection()->fd].ssl; + typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI; + for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) { + debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason); + + assert(i->error_no != SSL_ERROR_NONE); + + if (!errDetails) { + bool allowed = false; + if (check) { + check->sslErrors = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get())); + if (check->fastCheck() == ACCESS_ALLOWED) + allowed = true; + } + // else the Config.ssl_client.cert_error access list is not defined + // and the first error will cause the error page + + if (allowed) { + debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer"); + } else { + debugs(83, 5, "confirming SSL error " << i->error_no); + X509 *brokenCert = i->cert.get(); + Ssl::X509_Pointer peerCert(SSL_get_peer_certificate(ssl)); + const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str(); + errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason); + } + if (check) { + delete check->sslErrors; + check->sslErrors = NULL; + } + } + + if (!errs) + errs = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get())); + else + errs->push_back_unique(Ssl::CertError(i->error_no, i->cert.get())); + } + if (check) + delete check; + + return errs; + } + + /// A wrapper for Comm::SetSelect() notifications. + void + Ssl::PeerConnector::NegotiateSsl(int, void *data) + { + PeerConnector *pc = static_cast(data); + // Use job calls to add done() checks and other job logic/protections. + CallJobHere(83, 7, pc, Ssl::PeerConnector, negotiateSsl); + } + + void + Ssl::PeerConnector::handleNegotiateError(const int ret) + { + const int fd = serverConnection()->fd; + unsigned long ssl_lib_error = SSL_ERROR_NONE; + SSL *ssl = fd_table[fd].ssl; + int ssl_error = SSL_get_error(ssl, ret); ++ BIO *b = SSL_get_rbio(ssl); ++ Ssl::ServerBio *srvBio = static_cast(b->ptr); + + #ifdef EPROTO + int sysErrNo = EPROTO; + #else + int sysErrNo = EACCES; + #endif + + switch (ssl_error) { + + case SSL_ERROR_WANT_READ: + Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0); + return; + + 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(); ++ return; ++ } + Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0); + return; + + case SSL_ERROR_SSL: + case SSL_ERROR_SYSCALL: + ssl_lib_error = ERR_get_error(); + + // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1 + if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0) + sysErrNo = errno; + + debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd << + ": " << ERR_error_string(ssl_lib_error, NULL) << " (" << + ssl_error << "/" << ret << "/" << errno << ")"); + + break; // proceed to the general error handling code + + default: + break; // no special error handling for all other errors + } + + ErrorState *const anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw()); + anErr->xerrno = sysErrNo; + + Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail); + if (errFromFailure != NULL) { + // The errFromFailure is attached to the ssl object + // and will be released when ssl object destroyed. + // Copy errFromFailure to a new Ssl::ErrorDetail object + anErr->detail = new Ssl::ErrorDetail(*errFromFailure); + } else { + // server_cert can be NULL here + X509 *server_cert = SSL_get_peer_certificate(ssl); + anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL); + X509_free(server_cert); + } + + if (ssl_lib_error != SSL_ERROR_NONE) + anErr->detail->setLibError(ssl_lib_error); + + if (request->clientConnectionManager.valid()) { + // remember the server certificate from the ErrorDetail object + if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) { + serverBump->serverCert.resetAndLock(anErr->detail->peerCert()); + + // remember validation errors, if any + if (Ssl::CertErrors *errs = static_cast(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors))) + serverBump->sslErrors = cbdataReference(errs); + } + + // For intercepted connections, set the host name to the server + // certificate CN. Otherwise, we just hope that CONNECT is using + // a user-entered address (a host name or a user-entered IP). + const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted(); + if (request->flags.sslPeek && !isConnectRequest) { + if (X509 *srvX509 = anErr->detail->peerCert()) { + if (const char *name = Ssl::CommonHostName(srvX509)) { + request->SetHost(name); + debugs(83, 3, HERE << "reset request host: " << name); + } + } + } + } + + bail(anErr); + } + + void + Ssl::PeerConnector::bail(ErrorState *error) + { + Must(error); // or the recepient will not know there was a problem + + // XXX: forward.cc calls peerConnectSucceeded() after an OK TCP connect but + // we call peerConnectFailed() if SSL failed afterwards. Is that OK? + // It is not clear whether we should call peerConnectSucceeded/Failed() + // based on TCP results, SSL results, or both. And the code is probably not + // consistent in this aspect across tunnelling and forwarding modules. + if (CachePeer *p = serverConnection()->getPeer()) + peerConnectFailed(p); + + Must(callback != NULL); + CbDialer *dialer = dynamic_cast(callback->getDialer()); + Must(dialer); + dialer->answer().error = error; + + callBack(); + // Our job is done. The callabck recepient will probably close the failed + // peer connection and try another peer or go direct (if possible). We + // can close the connection ourselves (our error notification would reach + // the recepient before the fd-closure notification), but we would rather + // minimize the number of fd-closure notifications and let the recepient + // manage the TCP state of the connection. + } + + void + Ssl::PeerConnector::callBack() + { + AsyncCall::Pointer cb = callback; + // Do this now so that if we throw below, swanSong() assert that we _tried_ + // to call back holds. + callback = NULL; // this should make done() true + + // remove close handler + comm_remove_close_handler(serverConnection()->fd, closeHandler); + + CbDialer *dialer = dynamic_cast(cb->getDialer()); + Must(dialer); + dialer->answer().conn = serverConnection(); + ScheduleCallHere(cb); + } + + + void + Ssl::PeerConnector::swanSong() + { + // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any + AsyncJob::swanSong(); + assert(!callback); // paranoid: we have not left the caller waiting + } + + const char * + Ssl::PeerConnector::status() const + { + static MemBuf buf; + buf.reset(); + + // TODO: redesign AsyncJob::status() API to avoid this + // id and stop reason reporting duplication. + buf.append(" [", 2); + if (stopReason != NULL) { + buf.Printf("Stopped, reason:"); + buf.Printf("%s",stopReason); + } + if (serverConn != NULL) + buf.Printf(" FD %d", serverConn->fd); + buf.Printf(" %s%u]", id.Prefix, id.value); + buf.terminate(); + + return buf.content(); + } + + /* PeerConnectorAnswer */ + + Ssl::PeerConnectorAnswer::~PeerConnectorAnswer() + { + delete error.get(); + } + + std::ostream & + operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &answer) + { + return os << answer.conn << ", " << answer.error; + } diff --cc src/ssl/PeerConnector.h index 0000000000,820026df24..44c94eb51e mode 000000,100644..100644 --- a/src/ssl/PeerConnector.h +++ b/src/ssl/PeerConnector.h @@@ -1,0 -1,170 +1,172 @@@ + /* + * SQUID Web Proxy Cache http://www.squid-cache.org/ + * ---------------------------------------------------------- + * + * Squid is the result of efforts by numerous individuals from + * the Internet community; see the CONTRIBUTORS file for full + * details. Many organizations have provided support for Squid's + * development; see the SPONSORS file for full details. Squid is + * Copyrighted (C) 2001 by the Regents of the University of + * California; see the COPYRIGHT file for full details. Squid + * incorporates software developed and/or copyrighted by other + * sources; see the CREDITS file for full details. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + * + */ + #ifndef SQUID_SSL_PEER_CONNECTOR_H + #define SQUID_SSL_PEER_CONNECTOR_H + + #include "base/AsyncJob.h" + #include "base/AsyncCbdataCalls.h" + #include "ssl/support.h" + #include + + class HttpRequest; + class ErrorState; + + namespace Ssl { + + class ErrorDetail; + class CertValidationResponse; + + /// PeerConnector results (supplied via a callback). + /// The connection to peer was secured if and only if the error member is nil. + class PeerConnectorAnswer { + public: + ~PeerConnectorAnswer(); ///< deletes error if it is still set + Comm::ConnectionPointer conn; ///< peer connection (secured on success) + + /// answer recepients must clear the error member in order to keep its info + /// XXX: We should refcount ErrorState instead of cbdata-protecting it. + CbcPointer error; ///< problem details (nil on success) + }; + + /** + \par + * Connects Squid client-side to an SSL peer (cache_peer ... ssl). + * Handles peer certificate validation. + * Used by TunnelStateData, FwdState, and PeerPoolMgr to start talking to an + * SSL peer. + \par + * The caller receives a call back with PeerConnectorAnswer. If answer.error + * is not nil, then there was an error and the SSL connection to the SSL peer + * was not fully established. The error object is suitable for error response + * generation. + \par + * The caller must monitor the connection for closure because this + * job will not inform the caller about such events. + \par + * The caller must monitor the overall connection establishment timeout and + * close the connection on timeouts. This is probably better than having + * dedicated (or none at all!) timeouts for peer selection, DNS lookup, + * TCP handshake, SSL handshake, etc. Some steps may have their own timeout, + * but not all steps should be forced to have theirs. XXX: Neither tunnel.cc + * nor forward.cc have a "overall connection establishment" timeout. We need + * to change their code so that they start monitoring earlier and close on + * timeouts. This change may need to be discussed on squid-dev. + \par + * This job never closes the connection, even on errors. If a 3rd-party + * closes the connection, this job simply quits without informing the caller. + */ + class PeerConnector: virtual public AsyncJob + { + public: + /// Callback dialier API to allow PeerConnector to set the answer. + class CbDialer { + public: + virtual ~CbDialer() {} + /// gives PeerConnector access to the in-dialer answer + virtual PeerConnectorAnswer &answer() = 0; + }; + + typedef RefCount HttpRequestPointer; + + public: + PeerConnector(HttpRequestPointer &aRequest, + const Comm::ConnectionPointer &aServerConn, + AsyncCall::Pointer &aCallback); + virtual ~PeerConnector(); + + protected: + // AsyncJob API + virtual void start(); + virtual bool doneAll() const; + virtual void swanSong(); + virtual const char *status() const; + + /// The comm_close callback handler. + void commCloseHandler(const CommCloseCbParams ¶ms); + + /// Inform us that the connection is closed. Does the required clean-up. + void connectionClosed(const char *reason); + + /// Sets up TCP socket-related notification callbacks if things go wrong. + /// If socket already closed return false, else install the comm_close + /// handler to monitor the socket. + bool prepareSocket(); + + void initializeSsl(); ///< Initializes SSL state + + /// Performs a single secure connection negotiation step. + /// It is called multiple times untill the negotiation finish or aborted. + void negotiateSsl(); + ++ void checkForPeekAndSplice(); ++ + /// Called when the SSL negotiation step aborted because data needs to + /// be transferred to/from SSL server or on error. In the first case + /// setups the appropriate Comm::SetSelect handler. In second case + /// fill an error and report to the PeerConnector caller. + void handleNegotiateError(const int result); + + private: + PeerConnector(const PeerConnector &); // not implemented + PeerConnector &operator =(const PeerConnector &); // not implemented + + /// mimics FwdState to minimize changes to FwdState::initiate/negotiateSsl + Comm::ConnectionPointer const &serverConnection() const { return serverConn; } + + void bail(ErrorState *error); ///< Return an error to the PeerConnector caller + + /// Callback the caller class, and pass the ready to communicate secure + /// connection or an error if PeerConnector failed. + void callBack(); + + /// Process response from cert validator helper + void sslCrtvdHandleReply(Ssl::CertValidationResponse const &); + + /// Check SSL errors returned from cert validator against sslproxy_cert_error access list + Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&); + + /// Callback function called when squid receive message from cert validator helper + static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &); + + /// A wrapper function for negotiateSsl for use with Comm::SetSelect + static void NegotiateSsl(int fd, void *data); + + HttpRequestPointer request; ///< peer connection trigger or cause + Comm::ConnectionPointer serverConn; ///< TCP connection to the peer + AsyncCall::Pointer callback; ///< we call this with the results + AsyncCall::Pointer closeHandler; ///< we call this when the connection closed + + CBDATA_CLASS2(PeerConnector); + }; + + } // namespace Ssl + + std::ostream &operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &a); + + #endif /* SQUID_PEER_CONNECTOR_H */ diff --cc src/ssl/ServerBump.cc index 59eb761359,d2274faf39..3bcd338641 --- a/src/ssl/ServerBump.cc +++ b/src/ssl/ServerBump.cc @@@ -14,16 -14,15 +14,16 @@@ 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) { debugs(33, 4, HERE << "will peek at " << request->GetHost() << ':' << request->port); - const char *uri = urlCanonical(request); + const char *uri = urlCanonical(request.getRaw()); if (e) { entry = e; - entry->lock(); + entry->lock("Ssl::ServerBump"); } else entry = storeCreateEntry(uri, uri, request->flags, request->method); // We do not need to be a client because the error contents will be used diff --cc src/ssl/ServerBump.h index b681505507,c7955d13be..12c7185d29 --- a/src/ssl/ServerBump.h +++ b/src/ssl/ServerBump.h @@@ -27,8 -27,7 +27,8 @@@ public HttpRequest::Pointer request; StoreEntry *entry; ///< for receiving Squid-generated error messages Ssl::X509_Pointer serverCert; ///< HTTPS server certificate - Ssl::Errors *sslErrors; ///< SSL [certificate validation] errors + Ssl::CertErrors *sslErrors; ///< SSL [certificate validation] errors + Ssl::BumpMode mode; ///< The SSL server bump mode private: store_client *sc; ///< dummy client to prevent entry trimming diff --cc src/ssl/bio.cc index d697a67a0c,0000000000..9ec0bc9501 mode 100644,000000..100644 --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@@ -1,753 -1,0 +1,753 @@@ +/* + * DEBUG: section 83 SSL accelerator support + * + */ + +#include "squid.h" +#include "ssl/support.h" + +/* support.cc says this is needed */ - #if USE_SSL ++#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 + +// TODO: fde.h should probably export these for wrappers like ours +extern int default_read_method(int, char *, int); +extern int default_write_method(int, const char *, int); +#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 (headerState < 2) { + + if (rbuf.isNull()) + rbuf.init(1024, 4096); + + 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 (headerState == 0) { + + 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"); + headerBytes = (head[3] << 8) + head[4]; + debugs(83, 7, "SSL Header Size: " << headerBytes); +#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]; +#endif + }else { + debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)"); + return -1; + } + + headerState = 1; //Next state + } + + if (headerState == 1) { + 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()) { + BIO_set_retry_read(table); + return -1; + } + features.get((const unsigned char *)rbuf.content()); + headerState = 2; + } + + if (holdRead_) { + debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)"); + BIO_set_retry_read(table); + return -1; + } + + if (headerState >=2) { + 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::setClientRandom(const unsigned char *r) +{ + memcpy(clientRandom, r, SSL3_RANDOM_SIZE); + randomSet = 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, 8196); + rbuf.append(buf, bytes); + } + return bytes; +} + +int +Ssl::ServerBio::write(const char *buf, int size, BIO *table) +{ + + if (holdWrite_) { + debugs(83, 7, "Hold write, for SSL connection on " << fd_); + BIO_set_retry_write(table); + return -1; + } + + if (!helloBuild) { + 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"); + } + } + helloBuild = true; + helloMsgSize = helloMsg.contentSize(); + } + + if (helloMsg.hasContent()) { + debugs(83, 7, "buffered write for FD " << fd_); + int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), table); + helloMsg.consume(ret); + if (helloMsg.hasContent()) { + // 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; + // The size should be less than the size of the hello message + assert(size >= helloMsgSize); + return helloMsgSize; + } else + return Ssl::Bio::write(buf, size, table); +} + +void +Ssl::ServerBio::flush(BIO *table) +{ + if (helloMsg.hasContent()) { + int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), 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) +{ + 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(const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) + serverName = server; + debugs(83, 7, "SNI server name: " << serverName); + + 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); +#ifdef 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. + + //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); + } + } + } + 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 *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){ + 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); + } + ext += extLen; + } + } + } + return true; +} + +bool +Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello) +{ +#ifdef 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. + + //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 +} + +std::ostream & +Ssl::Bio::sslFeatures::print(std::ostream &os) const +{ + static std::string buf; + return os << "v" << sslVersion << + " SNI:" << (serverName.empty() ? "-" : serverName) << + " comp:" << compressMethod << + " Ciphers:" << clientRequestedCiphers << + " Random:" << objToString(client_random, SSL3_RANDOM_SIZE) << + " ecPointFormats:" << ecPointFormatList << + " ec:" << ellipticCurves << + " opaquePrf:" << opaquePrf; +} + +#endif /* USE_SSL */ diff --cc src/ssl/support.cc index a6be51a195,f944bf0c4e..33566e137a --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@@ -40,15 -40,15 +40,17 @@@ #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/support.h" #include "ssl/gadgets.h" + #include "ssl/support.h" #include "URL.h" #if HAVE_ERRNO_H @@@ -1077,33 -1071,6 +1141,29 @@@ Ssl::serverMethod(int version break; } + //Not reached + return NULL; +} + +SSL_CTX * +sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile) +{ + int ssl_error; - #if OPENSSL_VERSION_NUMBER < 0x00909000L - SSL_METHOD *method; - #else - const SSL_METHOD *method; - #endif - SSL_CTX *sslContext; ++ Ssl::ContextMethod method; ++ SSL_CTX * sslContext; + long fl = Ssl::parse_flags(flags); + + ssl_initialize(); + + if (!keyfile) + keyfile = certfile; + + if (!certfile) + certfile = keyfile; + + if (!(method = Ssl::method(version))) + return NULL; + sslContext = SSL_CTX_new(method); if (sslContext == NULL) { @@@ -1541,51 -1508,17 +1601,60 @@@ Ssl::generateSslContext(CertificateProp return createSSLContext(cert, pkey, port); } +bool +Ssl::configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!generateSslCertificate(cert, pkey, properties)) + return false; + + if (!cert) + return false; + + if (!pkey) + return false; + + if (!SSL_use_certificate(ssl, cert.get())) + return false; + + if (!SSL_use_PrivateKey(ssl, pkey.get())) + return false; + + return true; +} + +bool +Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!readCertAndPrivateKeyFromMemory(cert, pkey, data)) + return false; + + if (!cert || !pkey) + return false; + + if (!SSL_use_certificate(ssl, cert.get())) + return false; + + if (!SSL_use_PrivateKey(ssl, pkey.get())) + return false; + + return true; +} + bool Ssl::verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties) { + // SSL_get_certificate is buggy in openssl versions 1.0.1d and 1.0.1e + // Try to retrieve certificate directly from SSL_CTX object + #if SQUID_USE_SSLGETCERTIFICATE_HACK + X509 ***pCert = (X509 ***)sslContext->cert; + X509 * cert = pCert && *pCert ? **pCert : NULL; + #elif SQUID_SSLGETCERTIFICATE_BUGGY + X509 * cert = NULL; + assert(0); + #else // Temporary ssl for getting X509 certificate from SSL_CTX. Ssl::SSL_Pointer ssl(SSL_new(sslContext)); X509 * cert = SSL_get_certificate(ssl.get()); @@@ -1715,47 -1650,228 +1786,270 @@@ bool Ssl::generateUntrustedCert(X509_Po return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties); } +SSL * +SslCreate(SSL_CTX *sslContext, const int fd, Ssl::Bio::Type type, const char *squidCtx) +{ + const char *errAction = NULL; + int errCode = 0; + if (SSL *ssl = SSL_new(sslContext)) { + // without BIO, we would call SSL_set_fd(ssl, fd) instead + if (BIO *bio = Ssl::Bio::Create(fd, type)) { + Ssl::Bio::Link(ssl, bio); // cannot fail + + fd_table[fd].ssl = ssl; + fd_table[fd].read_method = &ssl_read_method; + fd_table[fd].write_method = &ssl_write_method; + fd_note(fd, squidCtx); + + return ssl; + } + errCode = ERR_get_error(); + errAction = "failed to initialize I/O"; + SSL_free(ssl); + } else { + errCode = ERR_get_error(); + errAction = "failed to allocate handle"; + } + + debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction << + ": " << ERR_error_string(errCode, NULL)); + return NULL; +} + +SSL * +Ssl::CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx) +{ + return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_SERVER, squidCtx); +} + +SSL * +Ssl::CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx) +{ + return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_CLIENT, squidCtx); +} + + Ssl::CertError::CertError(ssl_error_t anErr, X509 *aCert): code(anErr) + { + cert.resetAndLock(aCert); + } + + Ssl::CertError::CertError(CertError const &err): code(err.code) + { + cert.resetAndLock(err.cert.get()); + } + + Ssl::CertError & + Ssl::CertError::operator = (const CertError &old) + { + code = old.code; + cert.resetAndLock(old.cert.get()); + return *this; + } + + bool + Ssl::CertError::operator == (const CertError &ce) const + { + return code == ce.code && cert.get() == ce.cert.get(); + } + + bool + Ssl::CertError::operator != (const CertError &ce) const + { + return code != ce.code || cert.get() != ce.cert.get(); + } + + static int + store_session_cb(SSL *ssl, SSL_SESSION *session) + { + if (!SslSessionCache) + return 0; + + debugs(83, 5, "Request to store SSL Session "); + + SSL_SESSION_set_timeout(session, Config.SSL.session_ttl); + + unsigned char *id = session->session_id; + unsigned int idlen = session->session_id_length; + unsigned char key[MEMMAP_SLOT_KEY_SIZE]; + // Session ids are of size 32bytes. They should always fit to a + // MemMap::Slot::key + assert(idlen <= MEMMAP_SLOT_KEY_SIZE); + memset(key, 0, sizeof(key)); + memcpy(key, id, idlen); + int pos; + Ipc::MemMap::Slot *slotW = SslSessionCache->openForWriting((const cache_key*)key, pos); + if (slotW) { + int lenRequired = i2d_SSL_SESSION(session, NULL); + if (lenRequired < MEMMAP_SLOT_DATA_SIZE) { + unsigned char *p = (unsigned char *)slotW->p; + lenRequired = i2d_SSL_SESSION(session, &p); + slotW->set(key, NULL, lenRequired, squid_curtime + Config.SSL.session_ttl); + } + SslSessionCache->closeForWriting(pos); + debugs(83, 5, "wrote an ssl session entry of size " << lenRequired << " at pos " << pos); + } + return 0; + } + + static void + remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID) + { + if (!SslSessionCache) + return ; + + debugs(83, 5, "Request to remove corrupted or not valid SSL Session "); + int pos; + Ipc::MemMap::Slot const *slot = SslSessionCache->openForReading((const cache_key*)sessionID, pos); + if (slot == NULL) + return; + SslSessionCache->closeForReading(pos); + // TODO: + // What if we are not able to remove the session? + // Maybe schedule a job to remove it later? + // For now we just have an invalid entry in cache until will be expired + // The openSSL will reject it when we try to use it + SslSessionCache->free(pos); + } + + static SSL_SESSION * + get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy) + { + if (!SslSessionCache) + return NULL; + + SSL_SESSION *session = NULL; + const unsigned int *p; + p = (unsigned int *)sessionID; + debugs(83, 5, "Request to search for SSL Session of len:" << + len << p[0] << ":" << p[1]); + + int pos; + Ipc::MemMap::Slot const *slot = SslSessionCache->openForReading((const cache_key*)sessionID, pos); + if (slot != NULL) { + if (slot->expire > squid_curtime) { + const unsigned char *ptr = slot->p; + session = d2i_SSL_SESSION(NULL, &ptr, slot->pSize); + debugs(83, 5, "Session retrieved from cache at pos " << pos); + } else + debugs(83, 5, "Session in cache expired"); + SslSessionCache->closeForReading(pos); + } + + if (!session) + debugs(83, 5, "Failed to retrieved from cache\n"); + + // With the parameter copy the callback can require the SSL engine + // to increment the reference count of the SSL_SESSION object, Normally + // the reference count is not incremented and therefore the session must + // not be explicitly freed with SSL_SESSION_free(3). + *copy = 0; + return session; + } + + static void + setSessionCallbacks(SSL_CTX *ctx) + { + if (SslSessionCache) { + SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL); + SSL_CTX_sess_set_new_cb(ctx, store_session_cb); + SSL_CTX_sess_set_remove_cb(ctx, remove_session_cb); + SSL_CTX_sess_set_get_cb(ctx, get_session_cb); + } + } + + static bool + isSslServer() + { + if (Config.Sockaddr.https) + return true; + + for (AnyP::PortCfg *s = Config.Sockaddr.http; s; s = s->next) { + if (s->flags.tunnelSslBumping) + return true; + } + + return false; + } + + #define SSL_SESSION_ID_SIZE 32 + #define SSL_SESSION_MAX_SIZE 10*1024 + + void + Ssl::initialize_session_cache() + { + + if (!isSslServer()) //no need to configure ssl session cache. + return; + + // Check if the MemMap keys and data are enough big to hold + // session ids and session data + assert(SSL_SESSION_ID_SIZE >= MEMMAP_SLOT_KEY_SIZE); + assert(SSL_SESSION_MAX_SIZE >= MEMMAP_SLOT_DATA_SIZE); + + int configuredItems = ::Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot); + if (IamWorkerProcess() && configuredItems) + SslSessionCache = new Ipc::MemMap(SslSessionCacheName); + else { + SslSessionCache = NULL; + return; + } + + for (AnyP::PortCfg *s = ::Config.Sockaddr.https; s; s = s->next) { + if (s->staticSslContext.get() != NULL) + setSessionCallbacks(s->staticSslContext.get()); + } + + for (AnyP::PortCfg *s = ::Config.Sockaddr.http; s; s = s->next) { + if (s->staticSslContext.get() != NULL) + setSessionCallbacks(s->staticSslContext.get()); + } + } + + void + destruct_session_cache() + { + delete SslSessionCache; + } + + /// initializes shared memory segments used by MemStore + class SharedSessionCacheRr: public Ipc::Mem::RegisteredRunner + { + public: + /* RegisteredRunner API */ + SharedSessionCacheRr(): owner(NULL) {} + virtual void useConfig(); + virtual ~SharedSessionCacheRr(); + + protected: + virtual void create(); + + private: + Ipc::MemMap::Owner *owner; + }; + + RunnerRegistrationEntry(SharedSessionCacheRr); + + void + SharedSessionCacheRr::useConfig() + { + Ipc::Mem::RegisteredRunner::useConfig(); + } + + void + SharedSessionCacheRr::create() + { + if (!isSslServer()) //no need to configure ssl session cache. + return; + + int items; + items = Config.SSL.sessionCacheSize / sizeof(Ipc::MemMap::Slot); + if (items) + owner = Ipc::MemMap::Init(SslSessionCacheName, items); + } + + SharedSessionCacheRr::~SharedSessionCacheRr() + { + delete owner; + } - #endif /* USE_SSL */ + #endif /* USE_OPENSSL */ diff --cc src/ssl/support.h index 61dc1fceec,7198f5bb81..c538411570 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@@ -74,14 -83,23 +83,31 @@@ typedef int ssl_error_t typedef CbDataList Errors; +/// Creates SSL Client connection structure and initializes SSL I/O (Comm and BIO). +/// On errors, emits DBG_IMPORTANT with details and returns NULL. +SSL *CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx); + +/// Creates SSL Server connection structure and initializes SSL I/O (Comm and BIO). +/// On errors, emits DBG_IMPORTANT with details and returns NULL. +SSL *CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx); + + /// An SSL certificate-related error. + /// Pairs an error code with the certificate experiencing the error. + class CertError + { + public: + ssl_error_t code; ///< certificate error code + X509_Pointer cert; ///< certificate with the above error code + CertError(ssl_error_t anErr, X509 *aCert); + CertError(CertError const &err); + CertError & operator = (const CertError &old); + bool operator == (const CertError &ce) const; + bool operator != (const CertError &ce) const; + }; + + /// Holds a list of certificate SSL errors + typedef CbDataList CertErrors; + } //namespace Ssl /// \ingroup ServerProtocolSSLAPI @@@ -281,15 -278,17 +307,27 @@@ int asn1timeToString(ASN1_TIME *tm, cha */ bool setClientSNI(SSL *ssl, const char *fqdn); +int OpenSSLtoSquidSSLVersion(int sslVersion); + +#if OPENSSL_VERSION_NUMBER < 0x00909000L +SSL_METHOD *method(int version); +#else +const SSL_METHOD *method(int version); +#endif + +const SSL_METHOD *serverMethod(int version); ++ + /** + \ingroup ServerProtocolSSLAPI + * Initializes the shared session cache if configured + */ + void initialize_session_cache(); + + /** + \ingroup ServerProtocolSSLAPI + * Destroy the shared session cache if configured + */ + void destruct_session_cache(); } //namespace Ssl #if _SQUID_WINDOWS_