netinet/in.h \
netinet/in_systm.h \
netinet/ip_fil_compat.h \
- netinet/tcp.h \
- openssl/engine.h \
- openssl/txt_db.h \
- ostream \
+ netinet/tcp.h \
+ openssl/bio.h \
+ openssl/err.h \
+ openssl/md5.h \
+ openssl/opensslv.h \
+ openssl/ssl.h \
+ openssl/x509v3.h \
paths.h \
poll.h \
pwd.h \
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;
- A->allow.kind = Ssl::bumpPeekAndSplice;
+ } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpPeekAndSplice]) == 0) {
++ 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 "
#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 <limits.h>
- #endif
- #if HAVE_MATH_H
- #include <math.h>
- #endif
- #if HAVE_LIMITS
+ #include <climits>
+ #include <cmath>
#include <limits>
- #endif
#if LINGERING_CLOSE
#define comm_close comm_lingering_close
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
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();
}
- FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request);
+/** 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<Ssl::ClientBio *>(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<Ssl::ClientBio *>(b->ptr);
+ bio->hold(true);
+}
+
+void
+ConnStateData::startPeekAndSpliceDone()
+{
++ 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<Ssl::ClientBio *>(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)
{
/// 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);
--- /dev/null
- SSL *ssl;
+ /*
+ * 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<CbDialer*>(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<Ssl::PeerConnector, CommCloseCbParams> Dialer;
+ closeHandler = JobCallback(9, 5, Dialer, this, Ssl::PeerConnector::commCloseHandler);
+ comm_add_close_handler(fd, closeHandler);
+ return true;
+ }
+
+ void
+ Ssl::PeerConnector::initializeSsl()
+ {
- if ((ssl = SSL_new(sslContext)) == NULL) {
+ 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);
+
- SSL_set_fd(ssl, fd);
-
++ 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;
+ }
+
-
- fd_table[fd].ssl = ssl;
- fd_table[fd].read_method = &ssl_read_method;
- fd_table[fd].write_method = &ssl_write_method;
+ 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<Ssl::ClientBio *>(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<Ssl::ServerBio *>(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);
+ }
+ }
+
+ 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::CertErrors *>(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::CertErrors *>(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<Ssl::ServerBio *>(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<PeerConnector*>(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<Ssl::ServerBio *>(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::CertErrors*>(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<CbDialer*>(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<CbDialer*>(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;
+ }
--- /dev/null
+ /*
+ * 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 <iosfwd>
+
+ 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<ErrorState> 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<HttpRequest> 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 */
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
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
--- /dev/null
- #if USE_SSL
+/*
+ * DEBUG: section 83 SSL accelerator support
+ *
+ */
+
+#include "squid.h"
+#include "ssl/support.h"
+
+/* support.cc says this is needed */
++#if USE_OPENSSL
+
+#include "comm.h"
+#include "ip/Address.h"
+#include "fde.h"
+#include "globals.h"
+#include "Mem.h"
+#include "ssl/bio.h"
+#if HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#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<Ssl::Bio*>(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<Ssl::Bio*>(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<Ssl::Bio*>(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<int*>(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<Ssl::Bio*>(table->ptr);
+ assert(bio);
+ if (arg2)
+ *static_cast<int*>(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<Ssl::Bio*>(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<Ssl::Bio*>(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 */
#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
break;
}
- #if OPENSSL_VERSION_NUMBER < 0x00909000L
- SSL_METHOD *method;
- #else
- const SSL_METHOD *method;
- #endif
- SSL_CTX *sslContext;
+ //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;
++ 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) {
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());
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 */
typedef CbDataList<Ssl::ssl_error_t> 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<Ssl::CertError> CertErrors;
+
} //namespace Ssl
/// \ingroup ServerProtocolSSLAPI
*/
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_