#include "ssl/support.h"
CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector);
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, BlindPeerConnector);
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector);
Ssl::PeerConnector::PeerConnector(
- HttpRequestPointer &aRequest,
const Comm::ConnectionPointer &aServerConn,
- const Comm::ConnectionPointer &aClientConn,
AsyncCall::Pointer &aCallback,
const time_t timeout):
AsyncJob("Ssl::PeerConnector"),
- request(aRequest),
serverConn(aServerConn),
- clientConn(aClientConn),
callback(aCallback),
negotiationTimeout(timeout),
startTime(squid_curtime),
- splice(false),
- resumingSession(false),
- serverCertificateHandled(false)
+ useCertValidator_(false)
{
// if this throws, the caller's cb dialer is not our CbDialer
Must(dynamic_cast<CbDialer*>(callback->getDialer()));
Ssl::PeerConnector::~PeerConnector()
{
+ cbdataReferenceDone(certErrors);
debugs(83, 5, "Peer connector " << this << " gone");
}
{
AsyncJob::start();
- if (prepareSocket()) {
- initializeSsl();
+ if (prepareSocket() && (initializeSsl() != NULL))
negotiateSsl();
- }
}
void
return true;
}
-void
+SSL *
Ssl::PeerConnector::initializeSsl()
{
- SSL_CTX *sslContext = NULL;
- const CachePeer *peer = serverConnection()->getPeer();
- const int fd = serverConnection()->fd;
-
- if (peer) {
- assert(peer->secure.encryptTransport);
- sslContext = peer->sslContext;
- } else {
- // XXX: locate a per-server context in Security:: instead
- sslContext = ::Config.ssl_client.sslContext;
- }
-
+ SSL_CTX *sslContext = getSslContext();
assert(sslContext);
+ const int fd = serverConnection()->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;
- }
-
- if (peer) {
- // NP: domain may be a raw-IP but it is now always set
- assert(!peer->secure.sslDomain.isEmpty());
-
- // const loss is okay here, ssl_ex_index_server is only read and not assigned a destructor
- SBuf *host = new SBuf(peer->secure.sslDomain);
- SSL_set_ex_data(ssl, ssl_ex_index_server, host);
-
- if (peer->sslSession)
- SSL_set_session(ssl, peer->sslSession);
- } else if (ConnStateData *csd = request->clientConnectionManager.valid()) {
- // client connection is required in the case we need to splice
- // or terminate client and server connections
- assert(clientConn != NULL);
- SBuf *hostName = NULL;
- Ssl::ClientBio *cltBio = NULL;
-
- //Enable Status_request tls extension, required to bump some clients
- SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
-
- // In server-first bumping mode, clientSsl is NULL.
- if (SSL *clientSsl = fd_table[clientConn->fd].ssl) {
- BIO *b = SSL_get_rbio(clientSsl);
- cltBio = static_cast<Ssl::ClientBio *>(b->ptr);
- const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
- if (!features.serverName.isEmpty())
- hostName = new SBuf(features.serverName);
- }
-
- if (!hostName) {
- // 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 bool isConnectRequest = !csd->port->flags.isIntercepted();
- if (!request->flags.sslPeek || isConnectRequest)
- hostName = new SBuf(request->GetHost());
- }
-
- if (hostName)
- SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostName);
- Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
- if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
- assert(cltBio);
- const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
- if (features.sslVersion != -1) {
- features.applyToSSL(ssl, csd->sslBumpMode);
- // Should we allow it for all protocols?
- if (features.sslVersion >= 3) {
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
- // Inherite client features, like SSL version, SNI and other
- srvBio->setClientFeatures(features);
- srvBio->recordInput(true);
- srvBio->mode(csd->sslBumpMode);
- }
- }
- } else {
- // Set client SSL options
- SSL_set_options(ssl, ::Security::ProxyOutgoingConfig.parsedOptions);
-
- // Use SNI TLS extension only when we connect directly
- // to the origin server and we know the server host name.
- const char *sniServer = hostName ? hostName->c_str() :
- (!request->GetHostIsNumeric() ? request->GetHost() : NULL);
- if (sniServer)
- Ssl::setClientSNI(ssl, sniServer);
- }
+ noteNegotiationDone(anErr);
+ bail(anErr);
+ return NULL;
}
// If CertValidation Helper used do not lookup checklist for errors,
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);
- }
+ return ssl;
}
void
return; // we might be gone by now
}
- 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 (!sslFinalized())
return;
callBack();
}
-void
-Ssl::PeerConnector::handleServerCertificate()
-{
- if (serverCertificateHandled)
- return;
-
- if (ConnStateData *csd = request->clientConnectionManager.valid()) {
- const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
- Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
- if (!serverCert.get())
- return;
-
- serverCertificateHandled = true;
-
- csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
- debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
- " bumped: " << *serverConnection());
-
- // remember the server certificate for later use
- if (Ssl::ServerBump *serverBump = csd->serverBump()) {
- serverBump->serverCert.reset(serverCert.release());
- }
- }
-}
-
bool
Ssl::PeerConnector::sslFinalized()
{
- const int fd = serverConnection()->fd;
- SSL *ssl = fd_table[fd].ssl;
-
- // In the case the session is resuming, the certificates does not exist and
- // we did not do any cert validation
- if (resumingSession)
- return true;
-
- handleServerCertificate();
-
- if (ConnStateData *csd = request->clientConnectionManager.valid()) {
- if (Ssl::ServerBump *serverBump = csd->serverBump()) {
- // 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 (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].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
"validate that certificate.");
// fall through to do blocking in-process generation.
ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
+
+ noteNegotiationDone(anErr);
bail(anErr);
- if (serverConnection()->getPeer()) {
- peerConnectFailed(serverConnection()->getPeer());
- }
serverConn->close();
return true;
}
}
+
+ noteNegotiationDone(NULL);
return true;
}
void switchToTunnel(HttpRequest *request, Comm::ConnectionPointer & clientConn, Comm::ConnectionPointer &srvConn);
void
-Ssl::PeerConnector::cbCheckForPeekAndSpliceDone(allow_t answer, void *data)
+Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone(allow_t answer, void *data)
{
- Ssl::PeerConnector *peerConnect = (Ssl::PeerConnector *) data;
+ Ssl::PeekingPeerConnector *peerConnect = (Ssl::PeekingPeerConnector *) data;
peerConnect->checkForPeekAndSpliceDone((Ssl::BumpMode)answer.kind);
}
void
-Ssl::PeerConnector::checkForPeekAndSplice()
+Ssl::PeekingPeerConnector::checkForPeekAndSplice()
{
// Mark Step3 of bumping
if (request->clientConnectionManager.valid()) {
ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(
::Config.accessList.ssl_bump,
request.getRaw(), NULL);
- acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSpliceDone, this);
+ acl_checklist->nonBlockingCheck(Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone, this);
}
void
-Ssl::PeerConnector::checkForPeekAndSpliceDone(Ssl::BumpMode const action)
+Ssl::PeekingPeerConnector::checkForPeekAndSpliceDone(Ssl::BumpMode const action)
{
SSL *ssl = fd_table[serverConn->fd].ssl;
BIO *b = SSL_get_rbio(ssl);
//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);
+ Ssl::PeerConnector::noteWantWrite();
} else {
splice = true;
// Ssl Negotiation stops here. Last SSL checks for valid certificates
// and if done, switch to tunnel mode
- if (sslFinalized())
- switchToTunnel(request.getRaw(), clientConn, serverConn);
+ if (sslFinalized()) {
+ debugs(83,5, "Abort NegotiateSSL on FD " << serverConn->fd << " and splice the connection");
+ }
}
}
validatorFailed = true;
if (!errDetails && !validatorFailed) {
- if (splice)
- switchToTunnel(request.getRaw(), clientConn, serverConn);
- else
- callBack();
+ noteNegotiationDone(NULL);
+ callBack();
return;
}
+ if (errs) {
+ if (certErrors)
+ cbdataReferenceDone(certErrors);
+ certErrors = cbdataReference(errs);
+ }
+
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*/
}
+ noteNegotiationDone(anErr);
bail(anErr);
- if (serverConnection()->getPeer()) {
- peerConnectFailed(serverConnection()->getPeer());
- }
serverConn->close();
return;
}
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:
- setReadTimeout();
- Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
+ noteWantRead();
return;
case SSL_ERROR_WANT_WRITE:
- if ((srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && 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);
+ noteWantWrite();
return;
case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
ssl_lib_error = ERR_get_error();
+ // proceed to the general error handling code
+ break;
+ default:
+ // no special error handling for all other errors
+ break;
+ }
+ noteSslNegotiationError(ret, ssl_error, ssl_lib_error);
+}
- // In Peek mode, the ClientHello message sent to the server. If the
- // server resuming a previous (spliced) SSL session with the client,
- // then probably we are here because local SSL object does not know
- // anything about the session being resumed.
- //
- if (srvBio->bumpMode() == Ssl::bumpPeek && (resumingSession = srvBio->resumingSession())) {
- // we currently splice all resumed sessions unconditionally
- if (const bool spliceResumed = true) {
- checkForPeekAndSpliceDone(Ssl::bumpSplice);
- return;
- } // else fall through to find a matching ssl_bump action (with limited info)
- }
-
- // If we are in peek-and-splice mode and still we did not write to
- // server yet, try to see if we should splice.
- // In this case the connection can be saved.
- // If the checklist decision is do not splice a new error will
- // occure in the next SSL_connect call, and we will fail again.
- // Abort on certificate validation errors to avoid splicing and
- // thus hiding them.
- // Abort if no certificate found probably because of malformed or
- // unsupported server Hello message (TODO: make configurable).
-#if 1
- if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) &&
- (srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
- Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
- if (serverCert.get()) {
- debugs(81, 3, "Error (" << ERR_error_string(ssl_lib_error, NULL) << ") but, hold write on SSL connection on FD " << fd);
- checkForPeekAndSplice();
- return;
- }
- }
-#endif
+void
+Ssl::PeerConnector::noteWantRead()
+{
+ setReadTimeout();
+ const int fd = serverConnection()->fd;
+ Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
+}
- // 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;
+void
+Ssl::PeerConnector::noteWantWrite()
+{
+ const int fd = serverConnection()->fd;
+ Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
+ return;
+}
- debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
- ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
- ssl_error << "/" << ret << "/" << errno << ")");
+void
+Ssl::PeerConnector::noteSslNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
+{
+#ifdef EPROTO
+ int sysErrNo = EPROTO;
+#else
+ int sysErrNo = EACCES;
+#endif
- break; // proceed to the general error handling code
+ // 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;
- default:
- break; // no special error handling for all other errors
- }
+ const int fd = serverConnection()->fd;
+ debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
+ ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
+ ssl_error << "/" << ret << "/" << errno << ")");
- ErrorState *const anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
+ ErrorState *anErr = NULL;
+ if (request != NULL)
+ anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
+ else
+ anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL);
anErr->xerrno = sysErrNo;
+ SSL *ssl = fd_table[fd].ssl;
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
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);
- }
- }
- }
- }
+ assert(certErrors == NULL);
+ // remember validation errors, if any
+ if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors*>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+ certErrors = cbdataReference(errs);
+ noteNegotiationDone(anErr);
bail(anErr);
}
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);
return buf.content();
}
+SSL_CTX *
+Ssl::BlindPeerConnector::getSslContext()
+{
+ if (const CachePeer *peer = serverConnection()->getPeer()) {
+ assert(peer->secure.encryptTransport);
+ SSL_CTX *sslContext = peer->sslContext;
+ return sslContext;
+ }
+ return NULL;
+}
+
+SSL *
+Ssl::BlindPeerConnector::initializeSsl()
+{
+ SSL *ssl = Ssl::PeerConnector::initializeSsl();
+ if (!ssl)
+ return NULL;
+
+ const CachePeer *peer = serverConnection()->getPeer();
+ assert(peer);
+
+ // NP: domain may be a raw-IP but it is now always set
+ assert(!peer->secure.sslDomain.isEmpty());
+
+ // const loss is okay here, ssl_ex_index_server is only read and not assigned a destructor
+ SBuf *host = new SBuf(peer->secure.sslDomain);
+ SSL_set_ex_data(ssl, ssl_ex_index_server, host);
+
+ if (peer->sslSession)
+ SSL_set_session(ssl, peer->sslSession);
+
+ return ssl;
+}
+
+void
+Ssl::BlindPeerConnector::noteNegotiationDone(ErrorState *error)
+{
+ if (error) {
+ // 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);
+ return;
+ }
+
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
+ if (serverConnection()->getPeer()->sslSession)
+ SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
+
+ serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
+ }
+}
+
+SSL_CTX *
+Ssl::PeekingPeerConnector::getSslContext()
+{
+ // XXX: locate a per-server context in Security:: instead
+ return ::Config.ssl_client.sslContext;
+}
+
+SSL *
+Ssl::PeekingPeerConnector::initializeSsl()
+{
+ SSL *ssl = Ssl::PeerConnector::initializeSsl();
+ if (!ssl)
+ return NULL;
+
+ if (ConnStateData *csd = request->clientConnectionManager.valid()) {
+
+ // client connection is required in the case we need to splice
+ // or terminate client and server connections
+ assert(clientConn != NULL);
+ SBuf *hostName = NULL;
+ Ssl::ClientBio *cltBio = NULL;
+
+ //Enable Status_request tls extension, required to bump some clients
+ SSL_set_tlsext_status_type(ssl, TLSEXT_STATUSTYPE_ocsp);
+
+ // In server-first bumping mode, clientSsl is NULL.
+ if (SSL *clientSsl = fd_table[clientConn->fd].ssl) {
+ BIO *b = SSL_get_rbio(clientSsl);
+ cltBio = static_cast<Ssl::ClientBio *>(b->ptr);
+ const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
+ if (!features.serverName.isEmpty())
+ hostName = new SBuf(features.serverName);
+ }
+
+ if (!hostName) {
+ // 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 bool isConnectRequest = !csd->port->flags.isIntercepted();
+ if (!request->flags.sslPeek || isConnectRequest)
+ hostName = new SBuf(request->GetHost());
+ }
+
+ if (hostName)
+ SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostName);
+
+ Must(!csd->serverBump() || csd->serverBump()->step <= Ssl::bumpStep2);
+ if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
+ assert(cltBio);
+ const Ssl::Bio::sslFeatures &features = cltBio->getFeatures();
+ if (features.sslVersion != -1) {
+ features.applyToSSL(ssl, csd->sslBumpMode);
+ // Should we allow it for all protocols?
+ if (features.sslVersion >= 3) {
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ // Inherite client features, like SSL version, SNI and other
+ srvBio->setClientFeatures(features);
+ srvBio->recordInput(true);
+ srvBio->mode(csd->sslBumpMode);
+ }
+ }
+ } else {
+ // Set client SSL options
+ SSL_set_options(ssl, ::Security::ProxyOutgoingConfig.parsedOptions);
+
+ // Use SNI TLS extension only when we connect directly
+ // to the origin server and we know the server host name.
+ const char *sniServer = hostName ? hostName->c_str() :
+ (!request->GetHostIsNumeric() ? request->GetHost() : NULL);
+ if (sniServer)
+ Ssl::setClientSNI(ssl, sniServer);
+ }
+
+ // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
+ X509 *peeked_cert;
+ if (csd->serverBump() &&
+ (peeked_cert = csd->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);
+ }
+ }
+
+ return ssl;
+}
+
+void
+Ssl::PeekingPeerConnector::noteNegotiationDone(ErrorState *error)
+{
+ SSL *ssl = fd_table[serverConnection()->fd].ssl;
+
+ // Check the list error with
+ if (!request->clientConnectionManager.valid() || ! ssl)
+ return;
+
+ // remember the server certificate from the ErrorDetail object
+ if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
+ // remember validation errors, if any
+ if (certErrors) {
+ if (serverBump->sslErrors)
+ cbdataReferenceDone(serverBump->sslErrors);
+ serverBump->sslErrors = cbdataReference(certErrors);
+ }
+
+ if (!serverBump->serverCert.get()) {
+ // remember the server certificate from the ErrorDetail object
+ if (error && error->detail && error->detail->peerCert())
+ serverBump->serverCert.resetAndLock(error->detail->peerCert());
+ else {
+ handleServerCertificate();
+ }
+ }
+
+ if (error) {
+ // 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 = serverBump->serverCert.get()) {
+ if (const char *name = Ssl::CommonHostName(srvX509)) {
+ request->SetHost(name);
+ debugs(83, 3, "reset request host: " << name);
+ }
+ }
+ }
+ }
+ }
+
+ if (!error && splice)
+ switchToTunnel(request.getRaw(), clientConn, serverConn);
+}
+
+void
+Ssl::PeekingPeerConnector::noteWantWrite()
+{
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+
+ if ((srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
+ debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd);
+ checkForPeekAndSplice();
+ return;
+ }
+
+ Ssl::PeerConnector::noteWantWrite();
+}
+
+void
+Ssl::PeekingPeerConnector::noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error)
+{
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+
+ // In Peek mode, the ClientHello message sent to the server. If the
+ // server resuming a previous (spliced) SSL session with the client,
+ // then probably we are here because local SSL object does not know
+ // anything about the session being resumed.
+ //
+ if (srvBio->bumpMode() == Ssl::bumpPeek && (resumingSession = srvBio->resumingSession())) {
+ // we currently splice all resumed sessions unconditionally
+ if (const bool spliceResumed = true) {
+ bypassCertValidator();
+ checkForPeekAndSpliceDone(Ssl::bumpSplice);
+ return;
+ } // else fall through to find a matching ssl_bump action (with limited info)
+ }
+
+ // If we are in peek-and-splice mode and still we did not write to
+ // server yet, try to see if we should splice.
+ // In this case the connection can be saved.
+ // If the checklist decision is do not splice a new error will
+ // occur in the next SSL_connect call, and we will fail again.
+ // Abort on certificate validation errors to avoid splicing and
+ // thus hiding them.
+ // Abort if no certificate found probably because of malformed or
+ // unsupported server Hello message (TODO: make configurable).
+ if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) &&
+ (srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
+ Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
+ if (serverCert.get()) {
+ debugs(81, 3, "Error (" << ERR_error_string(ssl_lib_error, NULL) << ") but, hold write on SSL connection on FD " << fd);
+ checkForPeekAndSplice();
+ return;
+ }
+ }
+
+ // else call parent noteNegotiationError to produce an error page
+ Ssl::PeerConnector::noteSslNegotiationError(result, ssl_error, ssl_lib_error);
+}
+
+void
+Ssl::PeekingPeerConnector::handleServerCertificate()
+{
+ if (serverCertificateHandled)
+ return;
+
+ if (ConnStateData *csd = request->clientConnectionManager.valid()) {
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
+ if (!serverCert.get())
+ return;
+
+ serverCertificateHandled = true;
+
+ csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
+ debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
+ " bumped: " << *serverConnection());
+
+ // remember the server certificate for later use
+ if (Ssl::ServerBump *serverBump = csd->serverBump()) {
+ serverBump->serverCert.reset(serverCert.release());
+ }
+ }
+}
/**
\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.
+ * Connects Squid to SSL/TLS-capable peers or services.
+ * Contains common code and interfaces of various specialized PeerConnectors,
+ * including peer certificate validation code.
\par
* The caller receives a call back with Security::EncryptorAnswer. If answer.error
* is not nil, then there was an error and the SSL connection to the SSL peer
\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
{
CBDATA_CLASS(PeerConnector);
typedef RefCount<HttpRequest> HttpRequestPointer;
public:
- PeerConnector(HttpRequestPointer &aRequest,
- const Comm::ConnectionPointer &aServerConn,
- const Comm::ConnectionPointer &aClientConn,
+ PeerConnector(const Comm::ConnectionPointer &aServerConn,
AsyncCall::Pointer &aCallback, const time_t timeout = 0);
virtual ~PeerConnector();
/// silent server
void setReadTimeout();
- void initializeSsl(); ///< Initializes SSL state
+ virtual SSL *initializeSsl(); ///< Initializes SSL state
/// Performs a single secure connection negotiation step.
/// It is called multiple times untill the negotiation finish or aborted.
/// Otherwise, returns true, regardless of negotiation success/failure.
bool sslFinalized();
- /// Initiates the ssl_bump acl check in step3 SSL bump step to decide
- /// about bumping, splicing or terminating the connection.
- void checkForPeekAndSplice();
-
- /// Callback function for ssl_bump acl check in step3 SSL bump step.
- /// Handles the final bumping decision.
- void checkForPeekAndSpliceDone(Ssl::BumpMode const);
-
/// 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
+ /// Called when the openSSL SSL_connect fnction request more data from
+ /// the remote SSL server. Sets the read timeout and sets the
+ /// Squid COMM_SELECT_READ handler.
+ void noteWantRead();
+
+ /// Called when the openSSL SSL_connect function needs to write data to
+ /// the remote SSL server. Sets the Squid COMM_SELECT_WRITE handler.
+ virtual void noteWantWrite();
+
+ /// Called when the SSL_connect function aborts with an SSL negotiation error
+ /// \param result the SSL_connect return code
+ /// \param ssl_error the error code returned from the SSL_get_error function
+ /// \param ssl_lib_error the error returned from the ERR_Get_Error function
+ virtual void noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error);
+
+ /// Called when the SSL negotiation to the server completed and the certificates
+ /// validated using the cert validator.
+ /// \param error if not NULL the SSL negotiation was aborted with an error
+ virtual void noteNegotiationDone(ErrorState *error) {}
+
+ /// Must implemented by the kid classes to return the SSL_CTX object to use
+ /// for building the SSL objects.
+ virtual SSL_CTX *getSslContext() = 0;
/// 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
+ /// If called the certificates validator will not used
+ void bypassCertValidator() {useCertValidator_ = false;}
+
+ HttpRequestPointer request; ///< peer connection trigger or cause
+ Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
+ /// Certificate errors found from SSL validation procedure or from cert
+ /// validator
+ Ssl::CertErrors *certErrors;
+private:
+ PeerConnector(const PeerConnector &); // not implemented
+ PeerConnector &operator =(const PeerConnector &); // not implemented
+
/// Callback the caller class, and pass the ready to communicate secure
/// connection or an error if PeerConnector failed.
void callBack();
/// Check SSL errors returned from cert validator against sslproxy_cert_error access list
Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&);
- /// Updates associated client connection manager members
- /// if the server certificate was received from the server.
- void handleServerCertificate();
-
/// 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);
+ AsyncCall::Pointer callback; ///< we call this with the results
+ AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
+ time_t negotiationTimeout; ///< the SSL connection timeout to use
+ time_t startTime; ///< when the peer connector negotiation started
+ bool useCertValidator_; ///< whether the certificate validator should bypassed
+};
+
+/// A simple PeerConnector for SSL/TLS cache_peers. No SslBump capabilities.
+class BlindPeerConnector: public PeerConnector {
+ CBDATA_CLASS(BlindPeerConnector);
+public:
+ BlindPeerConnector(HttpRequestPointer &aRequest,
+ const Comm::ConnectionPointer &aServerConn,
+ AsyncCall::Pointer &aCallback, const time_t timeout = 0): AsyncJob("Ssl::BlindPeerConnector"),
+ PeerConnector(aServerConn, aCallback, timeout) { request = aRequest; }
+
+ /* PeerConnector API */
+
+ /// Calls parent initializeSSL, configure the created SSL object to try reuse SSL session
+ /// and sets the hostname to use for certificates validation
+ virtual SSL *initializeSsl();
+
+ /// Return the configured SSL_CTX object
+ virtual SSL_CTX *getSslContext();
+
+ /// On error calls peerConnectFailed function, on success store the used SSL session
+ /// for later use
+ virtual void noteNegotiationDone(ErrorState *error);
+};
+
+/// A PeerConnector for HTTP origin servers. Capable of SslBumping.
+class PeekingPeerConnector: public PeerConnector {
+ CBDATA_CLASS(PeekingPeerConnector);
+public:
+ PeekingPeerConnector(HttpRequestPointer &aRequest,
+ const Comm::ConnectionPointer &aServerConn,
+ const Comm::ConnectionPointer &aClientConn,
+ AsyncCall::Pointer &aCallback, const time_t timeout = 0): AsyncJob("Ssl::PeekingPeerConnector"),
+ PeerConnector(aServerConn, aCallback, timeout), clientConn(aClientConn), splice(false), resumingSession(false), serverCertificateHandled(false) { request = aRequest; }
+
+ /* PeerConnector API */
+ virtual SSL *initializeSsl();
+ virtual SSL_CTX *getSslContext();
+ virtual void noteWantWrite();
+ virtual void noteSslNegotiationError(const int result, const int ssl_error, const int ssl_lib_error);
+ virtual void noteNegotiationDone(ErrorState *error);
+
+ /// Updates associated client connection manager members
+ /// if the server certificate was received from the server.
+ void handleServerCertificate();
+
+ /// Initiates the ssl_bump acl check in step3 SSL bump step to decide
+ /// about bumping, splicing or terminating the connection.
+ void checkForPeekAndSplice();
+
+ /// Callback function for ssl_bump acl check in step3 SSL bump step.
+ /// Handles the final bumping decision.
+ void checkForPeekAndSpliceDone(Ssl::BumpMode const);
/// A wrapper function for checkForPeekAndSpliceDone for use with acl
static void cbCheckForPeekAndSpliceDone(allow_t answer, void *data);
- HttpRequestPointer request; ///< peer connection trigger or cause
- Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
+private:
Comm::ConnectionPointer clientConn; ///< TCP connection to the client
AsyncCall::Pointer callback; ///< we call this with the results
AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
- time_t negotiationTimeout; ///< the ssl connection timeout to use
time_t startTime; ///< when the peer connector negotiation started
- bool splice; ///< Whether we are going to splice or not
+ bool splice; ///< whether we are going to splice or not
bool resumingSession; ///< whether it is an SSL resuming session connection
bool serverCertificateHandled; ///< whether handleServerCertificate() succeeded
};