author: Alex Rousskov <rousskov@measurement-factory.com>, Christos Tsantilas <chtsanti@users.sourceforge.net>
Ssl::PeerConnector class

This patch investigates the new Ssl::PeerConnector class. This class connects
Squid client-side to a SSL cache_peer or SSL server. It is used by 
TunnelStateData and FwdState to initiate and establish the SSL connection.
This class handles peer certificate validation.

The caller receives a call back with PeerConnectorAnswer. In the SSL connection
is not established because of an error, an error object suitable for error
response generation is attached to PeerConnectorAnser

The Ssl::PeerConnector class includes the old SSL initialization code from
FwdState class.

This is a Measurement Factory project
=== modified file 'src/FwdState.cc'
--- src/FwdState.cc	2014-03-31 06:57:27 +0000
+++ src/FwdState.cc	2014-04-11 10:55:23 +0000
@@ -59,62 +59,86 @@
 #include "internal.h"
 #include "ip/Intercept.h"
 #include "ip/QosConfig.h"
 #include "ip/tools.h"
 #include "MemObject.h"
 #include "mgr/Registration.h"
 #include "neighbors.h"
 #include "pconn.h"
 #include "PeerSelectState.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "StoreClient.h"
 #include "urn.h"
 #include "whois.h"
 #if USE_OPENSSL
 #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"
 #endif
 #if HAVE_ERRNO_H
 #include <errno.h>
 #endif
 
 static PSC fwdPeerSelectionCompleteWrapper;
 static CLCB fwdServerClosedWrapper;
-#if USE_OPENSSL
-static PF fwdNegotiateSSLWrapper;
-#endif
 static CNCB fwdConnectDoneWrapper;
 
 static OBJH fwdStats;
 
 #define MAX_FWD_STATS_IDX 9
 static int FwdReplyCodes[MAX_FWD_STATS_IDX + 1][Http::scInvalidHeader + 1];
 
 static PconnPool *fwdPconnPool = new PconnPool("server-side");
 CBDATA_CLASS_INIT(FwdState);
 
+#if USE_OPENSSL
+class FwdStatePeerAnswerDialer: public CallDialer, public Ssl::PeerConnector::CbDialer
+{
+public:
+    typedef void (FwdState::*Method)(Ssl::PeerConnectorAnswer &);
+
+    FwdStatePeerAnswerDialer(Method method, FwdState *fwd):
+        method_(method), fwd_(fwd), answer_() {}
+
+    /* CallDialer API */
+    virtual bool canDial(AsyncCall &call) { return fwd_.valid(); }
+    void dial(AsyncCall &call) { ((&(*fwd_))->*method_)(answer_); }
+    virtual void print(std::ostream &os) const {
+        os << '(' << fwd_.get() << ", " << answer_ << ')'; }
+
+    /* Ssl::PeerConnector::CbDialer API */
+    virtual Ssl::PeerConnectorAnswer &answer() { return answer_; }
+
+private:
+    Method method_;
+    CbcPointer<FwdState> fwd_;
+    Ssl::PeerConnectorAnswer answer_;
+};
+#endif
+
+
 void
 FwdState::abort(void* d)
 {
     FwdState* fwd = (FwdState*)d;
     Pointer tmp = fwd; // Grab a temporary pointer to keep the object alive during our scope.
 
     if (Comm::IsConnOpen(fwd->serverConnection())) {
         comm_remove_close_handler(fwd->serverConnection()->fd, fwdServerClosedWrapper, fwd);
         debugs(17, 3, HERE << "store entry aborted; closing " <<
                fwd->serverConnection());
         fwd->serverConnection()->close();
     } else {
         debugs(17, 7, HERE << "store entry aborted; no connection to close");
     }
     fwd->serverDestinations.clear();
     fwd->self = NULL;
 }
 
 /**** PUBLIC INTERFACE ********************************************************/
 
@@ -491,50 +515,40 @@
 }
 
 /**** CALLBACK WRAPPERS ************************************************************/
 
 static void
 fwdPeerSelectionCompleteWrapper(Comm::ConnectionList * unused, ErrorState *err, void *data)
 {
     FwdState *fwd = (FwdState *) data;
     if (err)
         fwd->fail(err);
     fwd->startConnectionOrFail();
 }
 
 static void
 fwdServerClosedWrapper(const CommCloseCbParams &params)
 {
     FwdState *fwd = (FwdState *)params.data;
     fwd->serverClosed(params.fd);
 }
 
-#if USE_OPENSSL
-static void
-fwdNegotiateSSLWrapper(int fd, void *data)
-{
-    FwdState *fwd = (FwdState *) data;
-    fwd->negotiateSSL(fd);
-}
-
-#endif
-
 void
 fwdConnectDoneWrapper(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno, void *data)
 {
     FwdState *fwd = (FwdState *) data;
     fwd->connectDone(conn, status, xerrno);
 }
 
 /**** PRIVATE *****************************************************************/
 
 /*
  * FwdState::checkRetry
  *
  * Return TRUE if the request SHOULD be retried.  This method is
  * called when the HTTP connection fails, or when the connection
  * is closed before server-side read the end of HTTP headers.
  */
 bool
 FwdState::checkRetry()
 {
     if (shutting_down)
@@ -629,440 +643,105 @@
 
 // If the Server quits before nibbling at the request body, the body sender
 // will not know (so that we can retry). Call this if we will not retry. We
 // will notify the sender so that it does not get stuck waiting for space.
 void
 FwdState::doneWithRetries()
 {
     if (request && request->body_pipe != NULL)
         request->body_pipe->expectNoConsumption();
 }
 
 // called by the server that failed after calling unregister()
 void
 FwdState::handleUnregisteredServerEnd()
 {
     debugs(17, 2, HERE << "self=" << self << " err=" << err << ' ' << entry->url());
     assert(!Comm::IsConnOpen(serverConn));
     retryOrBail();
 }
 
-#if USE_OPENSSL
-void
-FwdState::negotiateSSL(int fd)
-{
-    unsigned long ssl_lib_error = SSL_ERROR_NONE;
-    SSL *ssl = fd_table[fd].ssl;
-    int ret;
-
-    if ((ret = SSL_connect(ssl)) <= 0) {
-        int ssl_error = SSL_get_error(ssl, ret);
-#ifdef EPROTO
-        int sysErrNo = EPROTO;
-#else
-        int sysErrNo = EACCES;
-#endif
-
-        switch (ssl_error) {
-
-        case SSL_ERROR_WANT_READ:
-            Comm::SetSelect(fd, COMM_SELECT_READ, fwdNegotiateSSLWrapper, this, 0);
-            return;
-
-        case SSL_ERROR_WANT_WRITE:
-            Comm::SetSelect(fd, COMM_SELECT_WRITE, fwdNegotiateSSLWrapper, this, 0);
-            return;
-
-        case SSL_ERROR_SSL:
-        case SSL_ERROR_SYSCALL:
-            ssl_lib_error = ERR_get_error();
-            debugs(81, DBG_IMPORTANT, "fwdNegotiateSSL: Error negotiating SSL connection on FD " << fd <<
-                   ": " << ERR_error_string(ssl_lib_error, NULL) << " (" << ssl_error <<
-                   "/" << ret << "/" << errno << ")");
-
-            // 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;
-
-            // falling through to complete error handling
-
-        default:
-            // TODO: move into a method before merge
-            Ssl::ErrorDetail *errDetails;
-            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.
-                errDetails = new Ssl::ErrorDetail(*errFromFailure);
-            } else {
-                // server_cert can be NULL here
-                X509 *server_cert = SSL_get_peer_certificate(ssl);
-                errDetails = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
-                X509_free(server_cert);
-            }
-
-            if (ssl_lib_error != SSL_ERROR_NONE)
-                errDetails->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(errDetails->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 = errDetails->peerCert()) {
-                        if (const char *name = Ssl::CommonHostName(srvX509)) {
-                            request->SetHost(name);
-                            debugs(83, 3, HERE << "reset request host: " << name);
-                        }
-                    }
-                }
-            }
-
-            ErrorState *const anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
-            anErr->xerrno = sysErrNo;
-            anErr->detail = errDetails;
-            fail(anErr);
-
-            if (serverConnection()->getPeer()) {
-                peerConnectFailed(serverConnection()->getPeer());
-            }
-
-            serverConn->close();
-            return;
-        }
-    }
-
-    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(33, 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);
-            fail(anErr);
-            if (serverConnection()->getPeer()) {
-                peerConnectFailed(serverConnection()->getPeer());
-            }
-            serverConn->close();
-            self = NULL;
-            return;
-        }
-    }
-
-    dispatch();
-}
-
-void
-FwdState::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse)
-{
-    FwdState * fwd = (FwdState *)(data);
-    fwd->sslCrtvdHandleReply(validationResponse);
-}
-
-void
-FwdState::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) {
-        dispatch();
-        return;
-    }
-
-    ErrorState *anErr = NULL;
-    if (validatorFailed) {
-        anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request);
-    }  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 = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
-        anErr->detail = errDetails;
-        /*anErr->xerrno= Should preserved*/
-    }
-
-    fail(anErr);
-    if (serverConnection()->getPeer()) {
-        peerConnectFailed(serverConnection()->getPeer());
-    }
-    serverConn->close();
-    self = NULL;
-    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 *
-FwdState::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, 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;
-}
-
-void
-FwdState::initiateSSL()
-{
-    SSL *ssl;
-    SSL_CTX *sslContext = NULL;
-    const CachePeer *peer = serverConnection()->getPeer();
-    int fd = serverConnection()->fd;
-
-    if (peer) {
-        assert(peer->use_ssl);
-        sslContext = peer->sslContext;
-    } else {
-        sslContext = Config.ssl_client.sslContext;
-    }
-
-    assert(sslContext);
-
-    if ((ssl = SSL_new(sslContext)) == NULL) {
-        debugs(83, DBG_IMPORTANT, "fwdInitiateSSL: Error allocating handle: " << ERR_error_string(ERR_get_error(), NULL)  );
-        ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request);
-        // TODO: create Ssl::ErrorDetail with OpenSSL-supplied error code
-        fail(anErr);
-        self = NULL;		// refcounted
-        return;
-    }
-
-    SSL_set_fd(ssl, fd);
-
-    if (peer) {
-        if (peer->ssldomain)
-            SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain);
-
-#if NOT_YET
-
-        else if (peer->name)
-            SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name);
-
-#endif
-
-        else
-            SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host);
-
-        if (peer->sslSession)
-            SSL_set_session(ssl, peer->sslSession);
-
-    } else {
-        // 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, dash_str);
-            SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
-        }
-    }
-
-    // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
-    X509 *peeked_cert;
-    if (request->clientConnectionManager.valid() &&
-            request->clientConnectionManager->serverBump() &&
-            (peeked_cert = request->clientConnectionManager->serverBump()->serverCert.get())) {
-        CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509);
-        SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert);
-    }
-
-    fd_table[fd].ssl = ssl;
-    fd_table[fd].read_method = &ssl_read_method;
-    fd_table[fd].write_method = &ssl_write_method;
-    negotiateSSL(fd);
-}
-
-#endif
-
 void
 FwdState::connectDone(const Comm::ConnectionPointer &conn, comm_err_t status, int xerrno)
 {
     if (status != COMM_OK) {
         ErrorState *const anErr = makeConnectingError(ERR_CONNECT_FAIL);
         anErr->xerrno = xerrno;
         fail(anErr);
 
         /* it might have been a timeout with a partially open link */
         if (conn != NULL) {
             if (conn->getPeer())
                 peerConnectFailed(conn->getPeer());
 
             conn->close();
         }
         retryOrBail();
         return;
     }
 
     serverConn = conn;
     flags.connected_okay = true;
 
     debugs(17, 3, HERE << serverConnection() << ": '" << entry->url() << "'" );
 
     comm_add_close_handler(serverConnection()->fd, fwdServerClosedWrapper, this);
 
     if (serverConnection()->getPeer())
         peerConnectSucceded(serverConnection()->getPeer());
 
 #if USE_OPENSSL
     if (!request->flags.pinned) {
         if ((serverConnection()->getPeer() && serverConnection()->getPeer()->use_ssl) ||
                 (!serverConnection()->getPeer() && request->protocol == AnyP::PROTO_HTTPS) ||
                 request->flags.sslPeek) {
-            initiateSSL();
+
+            HttpRequest::Pointer requestPointer = request;
+            AsyncCall::Pointer callback = asyncCall(17,4,
+                "FwdState::ConnectedToPeer",
+                FwdStatePeerAnswerDialer(&FwdState::connectedToPeer, this));
+            Ssl::PeerConnector *connector =
+                new Ssl::PeerConnector(requestPointer, serverConnection(), callback);
+            AsyncJob::Start(connector); // will call our callback
             return;
         }
     }
 #endif
 
     dispatch();
 }
 
+#if USE_OPENSSL
+void
+FwdState::connectedToPeer(Ssl::PeerConnectorAnswer &answer)
+{
+    if (ErrorState *error = answer.error.get()) {
+        fail(error);
+        answer.error.clear(); // preserve error for errorSendComplete()
+        self = NULL;
+        return;
+    }
+
+    dispatch();
+}
+#endif
+
 void
 FwdState::connectTimeout(int fd)
 {
     debugs(17, 2, "fwdConnectTimeout: FD " << fd << ": '" << entry->url() << "'" );
     assert(serverDestinations[0] != NULL);
     assert(fd == serverDestinations[0]->fd);
 
     if (entry->isEmpty()) {
         ErrorState *anErr = new ErrorState(ERR_CONNECT_FAIL, Http::scGatewayTimeout, request);
         anErr->xerrno = ETIMEDOUT;
         fail(anErr);
 
         /* This marks the peer DOWN ... */
         if (serverDestinations[0]->getPeer())
             peerConnectFailed(serverDestinations[0]->getPeer());
     }
 
     if (Comm::IsConnOpen(serverDestinations[0])) {
         serverDestinations[0]->close();
     }

=== modified file 'src/FwdState.h'
--- src/FwdState.h	2014-03-30 12:00:34 +0000
+++ src/FwdState.h	2014-04-09 16:40:53 +0000
@@ -7,40 +7,41 @@
 #include "err_type.h"
 #include "fde.h"
 #include "http/StatusCode.h"
 #include "ip/Address.h"
 #if USE_OPENSSL
 #include "ssl/support.h"
 #endif
 
 /* forward decls */
 
 class AccessLogEntry;
 typedef RefCount<AccessLogEntry> AccessLogEntryPointer;
 class ErrorState;
 class HttpRequest;
 
 #if USE_OPENSSL
 namespace Ssl
 {
 class ErrorDetail;
 class CertValidationResponse;
+class PeerConnectorAnswer;
 };
 #endif
 
 /**
  * Returns the TOS value that we should be setting on the connection
  * to the server, based on the ACL.
  */
 tos_t GetTosToServer(HttpRequest * request);
 
 /**
  * Returns the Netfilter mark value that we should be setting on the
  * connection to the server, based on the ACL.
  */
 nfmark_t GetNfmarkToServer(HttpRequest * request);
 
 class HelperReply;
 
 class FwdState : public RefCountable
 {
 public:
@@ -52,75 +53,68 @@
     static void Start(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *, const AccessLogEntryPointer &alp);
     /// Same as Start() but no master xaction info (AccessLogEntry) available.
     static void fwdStart(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *);
 
     /// This is the real beginning of server connection. Call it whenever
     /// the forwarding server destination has changed and a new one needs to be opened.
     /// Produces the cannot-forward error on fail if no better error exists.
     void startConnectionOrFail();
 
     void fail(ErrorState *err);
     void unregister(Comm::ConnectionPointer &conn);
     void unregister(int fd);
     void complete();
     void handleUnregisteredServerEnd();
     int reforward();
     bool reforwardableStatus(const Http::StatusCode s) const;
     void serverClosed(int fd);
     void connectStart();
     void connectDone(const Comm::ConnectionPointer & conn, comm_err_t status, int xerrno);
     void connectTimeout(int fd);
-    void initiateSSL();
-    void negotiateSSL(int fd);
     bool checkRetry();
     bool checkRetriable();
     void dispatch();
     void pconnPush(Comm::ConnectionPointer & conn, const char *domain);
 
     bool dontRetry() { return flags.dont_retry; }
 
     void dontRetry(bool val) { flags.dont_retry = val; }
 
     /** return a ConnectionPointer to the current server connection (may or may not be open) */
     Comm::ConnectionPointer const & serverConnection() const { return serverConn; };
 
-#if USE_OPENSSL
-    /// Callback function called when squid receive message from cert validator helper
-    static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &);
-    /// 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 *&);
-#endif
 private:
     // hidden for safer management of self; use static fwdStart
     FwdState(const Comm::ConnectionPointer &client, StoreEntry *, HttpRequest *, const AccessLogEntryPointer &alp);
     void start(Pointer aSelf);
 
 #if STRICT_ORIGINAL_DST
     void selectPeerForIntercepted();
 #endif
     static void logReplyStatus(int tries, const Http::StatusCode status);
     void doneWithRetries();
     void completed();
     void retryOrBail();
     ErrorState *makeConnectingError(const err_type type) const;
+#if USE_OPENSSL
+    void connectedToPeer(Ssl::PeerConnectorAnswer &answer);
+#endif
     static void RegisterWithCacheManager(void);
 
 public:
     StoreEntry *entry;
     HttpRequest *request;
     AccessLogEntryPointer al; ///< info for the future access.log entry
 
     static void abort(void*);
 
 private:
     Pointer self;
     ErrorState *err;
     Comm::ConnectionPointer clientConn;        ///< a possibly open connection to the client.
     time_t start_t;
     int n_tries;
 
     // AsyncCalls which we set and may need cancelling.
     struct {
         AsyncCall::Pointer connector;  ///< a call linking us to the ConnOpener producing serverConn.
     } calls;

=== modified file 'src/errorpage.cc'
--- src/errorpage.cc	2014-03-31 06:57:27 +0000
+++ src/errorpage.cc	2014-04-03 15:27:57 +0000
@@ -551,40 +551,49 @@
         ErrorDynamicPages.push_back(info);
         id = info->id;
     }
 
     return (err_type)id;
 }
 
 /// \ingroup ErrorPageInternal
 const char *
 errorPageName(int pageId)
 {
     if (pageId >= ERR_NONE && pageId < ERR_MAX)		/* common case */
         return err_type_str[pageId];
 
     if (pageId >= ERR_MAX && pageId - ERR_MAX < (ssize_t)ErrorDynamicPages.size())
         return ErrorDynamicPages[pageId - ERR_MAX]->page_name;
 
     return "ERR_UNKNOWN";	/* should not happen */
 }
 
+ErrorState *
+ErrorState::NewForwarding(err_type type, HttpRequest *request)
+{
+    assert(request);
+    const Http::StatusCode status = request->flags.needValidation ?
+        Http::scGatewayTimeout : Http::scServiceUnavailable;
+    return new ErrorState(type, status, request);
+}
+
 ErrorState::ErrorState(err_type t, Http::StatusCode status, HttpRequest * req) :
         type(t),
         page_id(t),
         err_language(NULL),
         httpStatus(status),
 #if USE_AUTH
         auth_user_request (NULL),
 #endif
         request(NULL),
         url(NULL),
         xerrno(0),
         port(0),
         dnsError(),
         ttl(0),
         src_addr(),
         redirect_url(NULL),
         callback(NULL),
         callback_data(NULL),
         request_hdrs(NULL),
         err_msg(NULL),

=== modified file 'src/errorpage.h'
--- src/errorpage.h	2014-03-30 12:00:34 +0000
+++ src/errorpage.h	2014-04-03 14:55:21 +0000
@@ -84,40 +84,43 @@
    w - cachemgr email address                   x
    W - error data (to be included in the mailto links)
    x - error name                               x
    z - dns server error message                 x
    Z - Preformatted error message               x
  \endverbatim
  */
 
 class HttpReply;
 class HttpRequest;
 class MemBuf;
 
 /// \ingroup ErrorPageAPI
 class ErrorState
 {
 public:
     ErrorState(err_type type, Http::StatusCode, HttpRequest * request);
     ErrorState(); // not implemented.
     ~ErrorState();
 
+    /// Creates a general request forwarding error with the right http_status.
+    static ErrorState *NewForwarding(err_type type, HttpRequest *request);
+
     /**
      * Allocates and initializes an error response
      */
     HttpReply *BuildHttpReply(void);
 
     /// set error type-specific detail code
     void detailError(int dCode) {detailCode = dCode;}
 
 private:
     /**
      * Locates error page template to be used for this error
      * and constructs the HTML page content from it.
      */
     MemBuf *BuildContent(void);
 
     /**
      * Convert the given template string into textual output
      *
      * \param text            The string to be converted
      * \param allowRecursion  Whether to convert codes which output may contain codes

=== modified file 'src/ssl/Makefile.am'
--- src/ssl/Makefile.am	2012-11-24 14:12:30 +0000
+++ src/ssl/Makefile.am	2014-04-03 10:56:20 +0000
@@ -10,40 +10,42 @@
 	ssl_crtd.8
 
 if USE_SSL_CRTD
 SSL_CRTD = ssl_crtd
 else
 SSL_CRTD =
 endif
 
 ## SSL stuff used by main Squid but not by ssl_crtd
 libsslsquid_la_SOURCES = \
 	cert_validate_message.cc \
 	cert_validate_message.h \
 	context_storage.cc \
 	context_storage.h \
 	Config.cc \
 	Config.h \
 	ErrorDetail.cc \
 	ErrorDetail.h \
 	ErrorDetailManager.cc \
 	ErrorDetailManager.h \
+	PeerConnector.cc \
+	PeerConnector.h \
 	ProxyCerts.h \
 	ServerBump.cc \
 	ServerBump.h \
 	support.cc \
 	support.h \
 	helper.cc \
 	helper.h
 
 ## SSL stuff used by main Squid and ssl_crtd
 libsslutil_la_SOURCES = \
 	gadgets.cc \
 	gadgets.h \
 	crtd_message.cc \
 	crtd_message.h
 
 libexec_PROGRAMS = \
 	$(SSL_CRTD)
 
 if USE_SSL_CRTD
 ssl_crtd_SOURCES = ssl_crtd.cc certificate_db.cc certificate_db.h

=== added file 'src/ssl/PeerConnector.cc'
--- src/ssl/PeerConnector.cc	1970-01-01 00:00:00 +0000
+++ src/ssl/PeerConnector.cc	2014-04-12 17:41:10 +0000
@@ -0,0 +1,545 @@
+/*
+ * 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/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 &params)
+{
+    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()
+{
+    SSL *ssl;
+    SSL_CTX *sslContext = NULL;
+    const CachePeer *peer = serverConnection()->getPeer();
+    const int fd = serverConnection()->fd;
+
+    if (peer) {
+        assert(peer->use_ssl);
+        sslContext = peer->sslContext;
+    } else {
+        sslContext = ::Config.ssl_client.sslContext;
+    }
+
+    assert(sslContext);
+
+    if ((ssl = SSL_new(sslContext)) == NULL) {
+        ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw());
+        anErr->xerrno = errno;
+        debugs(83, DBG_IMPORTANT, "Error allocating SSL handle: " << ERR_error_string(ERR_get_error(), NULL));
+        bail(anErr);
+        return;
+    }
+
+    SSL_set_fd(ssl, fd);
+
+    if (peer) {
+        if (peer->ssldomain)
+            SSL_set_ex_data(ssl, ssl_ex_index_server, peer->ssldomain);
+
+#if NOT_YET
+
+        else if (peer->name)
+            SSL_set_ex_data(ssl, ssl_ex_index_server, peer->name);
+
+#endif
+
+        else
+            SSL_set_ex_data(ssl, ssl_ex_index_server, peer->host);
+
+        if (peer->sslSession)
+            SSL_set_session(ssl, peer->sslSession);
+
+    } else {
+        // While we are peeking at the certificate, we may not know the server
+        // name that the client will request (after interception or CONNECT)
+        // unless it was the CONNECT request with a user-typed address.
+        const char *hostname = request->GetHost();
+        const bool hostnameIsIp = request->GetHostIsNumeric();
+        const bool isConnectRequest = request->clientConnectionManager.valid() &&
+                                      !request->clientConnectionManager->port->flags.isIntercepted();
+        if (!request->flags.sslPeek || isConnectRequest)
+            SSL_set_ex_data(ssl, ssl_ex_index_server, (void*)hostname);
+
+        // Use SNI TLS extension only when we connect directly
+        // to the origin server and we know the server host name.
+        if (!hostnameIsIp)
+            Ssl::setClientSNI(ssl, hostname);
+    }
+
+    // If CertValidation Helper used do not lookup checklist for errors,
+    // but keep a list of errors to send it to CertValidator
+    if (!Ssl::TheConfig.ssl_crt_validator) {
+        // Create the ACL check list now, while we have access to more info.
+        // The list is used in ssl_verify_cb() and is freed in ssl_free().
+        if (acl_access *acl = ::Config.ssl_client.cert_error) {
+            ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
+            // check->fd(fd); XXX: need client FD here
+            SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
+        }
+    }
+
+    // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
+    X509 *peeked_cert;
+    if (request->clientConnectionManager.valid() &&
+            request->clientConnectionManager->serverBump() &&
+            (peeked_cert = request->clientConnectionManager->serverBump()->serverCert.get())) {
+        CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509);
+        SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert);
+    }
+
+    fd_table[fd].ssl = ssl;
+    fd_table[fd].read_method = &ssl_read_method;
+    fd_table[fd].write_method = &ssl_write_method;
+}
+
+void
+Ssl::PeerConnector::negotiateSsl()
+{
+    if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing())
+        return;
+
+    const int fd = serverConnection()->fd;
+    SSL *ssl = fd_table[fd].ssl;
+    const int result = SSL_connect(ssl);
+    if (result <= 0) {
+        handleNegotiateError(result);
+        return; // we might be gone by now
+    }
+
+    if (request->clientConnectionManager.valid()) {
+        // remember the server certificate from the ErrorDetail object
+        if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
+            serverBump->serverCert.reset(SSL_get_peer_certificate(ssl));
+
+            // remember validation errors, if any
+            if (Ssl::CertErrors *errs = static_cast<Ssl::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::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);
+
+#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:
+            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;
+}

=== added file 'src/ssl/PeerConnector.h'
--- src/ssl/PeerConnector.h	1970-01-01 00:00:00 +0000
+++ src/ssl/PeerConnector.h	2014-04-12 18:01:44 +0000
@@ -0,0 +1,170 @@
+/*
+ * 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 &params);
+
+    /// 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();
+
+    /// 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 */

=== modified file 'src/tunnel.cc'
--- src/tunnel.cc	2014-02-21 10:46:19 +0000
+++ src/tunnel.cc	2014-04-12 17:42:37 +0000
@@ -33,40 +33,43 @@
 
 #include "squid.h"
 #include "acl/FilledChecklist.h"
 #include "CachePeer.h"
 #include "client_side.h"
 #include "client_side_request.h"
 #include "comm.h"
 #include "comm/Connection.h"
 #include "comm/ConnOpener.h"
 #include "comm/Write.h"
 #include "errorpage.h"
 #include "fde.h"
 #include "http.h"
 #include "HttpRequest.h"
 #include "HttpStateFlags.h"
 #include "ip/QosConfig.h"
 #include "MemBuf.h"
 #include "PeerSelectState.h"
 #include "SquidConfig.h"
 #include "StatCounters.h"
+#if USE_OPENSSL
+#include "ssl/PeerConnector.h"
+#endif
 #include "tools.h"
 #if USE_DELAY_POOLS
 #include "DelayId.h"
 #endif
 
 #include <climits>
 #include <cerrno>
 
 /**
  * TunnelStateData is the state engine performing the tasks for
  * setup of a TCP tunnel from an existing open client FD to a server
  * then shuffling binary data between the resulting FD pair.
  */
 /*
  * TODO 1: implement a read/write API on ConnStateData to send/receive blocks
  * of pre-formatted data. Then we can use that as the client side of the tunnel
  * instead of re-implementing it here and occasionally getting the ConnStateData
  * read/write state wrong.
  *
  * TODO 2: then convert this into a AsyncJob, possibly a child of 'Server'
@@ -140,41 +143,72 @@
         char *buf;
         int64_t *size_ptr;		/* pointer to size in an ConnStateData for logging */
 
         Comm::ConnectionPointer conn;    ///< The currently connected connection.
 
     private:
 #if USE_DELAY_POOLS
 
         DelayId delayId;
 #endif
 
     };
 
     Connection client, server;
     int *status_ptr;		/* pointer to status for logging */
     MemBuf *connectRespBuf; ///< accumulates peer CONNECT response when we need it
     bool connectReqWriting; ///< whether we are writing a CONNECT request to a peer
 
     void copyRead(Connection &from, IOCB *completion);
 
+    /// continue to set up connection to a peer, going async for SSL peers
+    void connectToPeer();
+
 private:
+#if USE_OPENSSL
+    /// Gives PeerConnector access to Answer in the TunnelStateData callback dialer.
+    class MyAnswerDialer: public CallDialer, public Ssl::PeerConnector::CbDialer
+    {
+    public:
+        typedef void (TunnelStateData::*Method)(Ssl::PeerConnectorAnswer &);
+
+        MyAnswerDialer(Method method, TunnelStateData *tunnel):
+                       method_(method), tunnel_(tunnel), answer_() {}
+
+        /* CallDialer API */
+        virtual bool canDial(AsyncCall &call) { return tunnel_.valid(); }
+        void dial(AsyncCall &call) { ((&(*tunnel_))->*method_)(answer_); }
+        virtual void print(std::ostream &os) const {
+            os << '(' << tunnel_.get() << ", " << answer_ << ')'; }
+
+        /* Ssl::PeerConnector::CbDialer API */
+        virtual Ssl::PeerConnectorAnswer &answer() { return answer_; }
+
+    private:
+        Method method_;
+        CbcPointer<TunnelStateData> tunnel_;
+        Ssl::PeerConnectorAnswer answer_;
+    };
+
+    void connectedToPeer(Ssl::PeerConnectorAnswer &answer);
+#endif
+
     CBDATA_CLASS2(TunnelStateData);
     bool keepGoingAfterRead(size_t len, comm_err_t errcode, int xerrno, Connection &from, Connection &to);
     void copy(size_t len, Connection &from, Connection &to, IOCB *);
     void handleConnectResponse(const size_t chunkSize);
     void readServer(char *buf, size_t len, comm_err_t errcode, int xerrno);
     void readClient(char *buf, size_t len, comm_err_t errcode, int xerrno);
     void writeClientDone(char *buf, size_t len, comm_err_t flag, int xerrno);
     void writeServerDone(char *buf, size_t len, comm_err_t flag, int xerrno);
 
     static void ReadConnectResponseDone(const Comm::ConnectionPointer &, char *buf, size_t len, comm_err_t errcode, int xerrno, void *data);
     void readConnectResponseDone(char *buf, size_t len, comm_err_t errcode, int xerrno);
 };
 
 static const char *const conn_established = "HTTP/1.1 200 Connection established\r\n\r\n";
 
 static CNCB tunnelConnectDone;
 static ERCB tunnelErrorComplete;
 static CLCB tunnelServerClosed;
 static CLCB tunnelClientClosed;
 static CTCB tunnelTimeout;
@@ -810,41 +844,41 @@
     if (conn->getPeer() && conn->getPeer()->options.no_delay)
         tunnelState->server.setDelayId(DelayId());
 #endif
 
     tunnelState->request->hier.note(conn, tunnelState->getHost());
 
     tunnelState->server.conn = conn;
     tunnelState->request->peer_host = conn->getPeer() ? conn->getPeer()->host : NULL;
     comm_add_close_handler(conn->fd, tunnelServerClosed, tunnelState);
 
     debugs(26, 4, HERE << "determine post-connect handling pathway.");
     if (conn->getPeer()) {
         tunnelState->request->peer_login = conn->getPeer()->login;
         tunnelState->request->flags.proxying = !(conn->getPeer()->options.originserver);
     } else {
         tunnelState->request->peer_login = NULL;
         tunnelState->request->flags.proxying = false;
     }
 
     if (tunnelState->request->flags.proxying)
-        tunnelRelayConnectRequest(conn, tunnelState);
+        tunnelState->connectToPeer();
     else {
         tunnelConnected(conn, tunnelState);
     }
 
     AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
                                      CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
     commSetConnTimeout(conn, Config.Timeout.read, timeoutCall);
 }
 
 tos_t GetTosToServer(HttpRequest * request);
 nfmark_t GetNfmarkToServer(HttpRequest * request);
 
 void
 tunnelStart(ClientHttpRequest * http, int64_t * size_ptr, int *status_ptr, const AccessLogEntryPointer &al)
 {
     debugs(26, 3, HERE);
     /* Create state structure. */
     TunnelStateData *tunnelState = NULL;
     ErrorState *err = NULL;
     HttpRequest *request = http->request;
@@ -885,40 +919,79 @@
     tunnelState->request = request;
     tunnelState->server.size_ptr = size_ptr;
     tunnelState->status_ptr = status_ptr;
     tunnelState->client.conn = http->getConn()->clientConnection;
     tunnelState->al = al;
 
     comm_add_close_handler(tunnelState->client.conn->fd,
                            tunnelClientClosed,
                            tunnelState);
 
     AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",
                                      CommTimeoutCbPtrFun(tunnelTimeout, tunnelState));
     commSetConnTimeout(tunnelState->client.conn, Config.Timeout.lifetime, timeoutCall);
 
     peerSelect(&(tunnelState->serverDestinations), request, al,
                NULL,
                tunnelPeerSelectComplete,
                tunnelState);
 }
 
+void
+TunnelStateData::connectToPeer() {
+    const Comm::ConnectionPointer &srv = server.conn;
+
+#if USE_OPENSSL
+    if (CachePeer *p = srv->getPeer()) {
+        if (p->use_ssl) {
+            AsyncCall::Pointer callback = asyncCall(5,4,
+                "TunnelStateData::ConnectedToPeer",
+                MyAnswerDialer(&TunnelStateData::connectedToPeer, this));
+            Ssl::PeerConnector *connector =
+                new Ssl::PeerConnector(request, srv, callback);
+            AsyncJob::Start(connector); // will call our callback
+            return;
+        }
+    }
+#endif
+
+    tunnelRelayConnectRequest(srv, this);
+}
+
+#if USE_OPENSSL
+/// Ssl::PeerConnector callback
+void
+TunnelStateData::connectedToPeer(Ssl::PeerConnectorAnswer &answer)
+{
+    if (ErrorState *error = answer.error.get()) {
+        *status_ptr = error->httpStatus;
+        error->callback = tunnelErrorComplete;
+        error->callback_data = this;
+        errorSend(client.conn, error);
+        answer.error.clear(); // preserve error for errorSendComplete()
+        return;
+    }
+
+    tunnelRelayConnectRequest(server.conn, this);
+}
+#endif
+
 static void
 tunnelRelayConnectRequest(const Comm::ConnectionPointer &srv, void *data)
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     assert(!tunnelState->waitingForConnectExchange());
     HttpHeader hdr_out(hoRequest);
     Packer p;
     HttpStateFlags flags;
     debugs(26, 3, HERE << srv << ", tunnelState=" << tunnelState);
     memset(&flags, '\0', sizeof(flags));
     flags.proxying = tunnelState->request->flags.proxying;
     MemBuf mb;
     mb.init();
     mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url);
     HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(),
                                           NULL,			/* StoreEntry */
                                           tunnelState->al,			/* AccessLogEntry */
                                           &hdr_out,
                                           flags);			/* flags */
     packerToMemInit(&p, &mb);