This patch allows Squid to provide details for the %D macro on the secure connect failed error page when an SSL handshake with the origin server fails. The default %D text is "Handshake with SSL server failed: XYZ" where XYZ is the corresponding error string/description returned by OpenSSL if there is any.

This is a Measurement Factory project.
Detail SSL handshake failures

This patch allows Squid to provide details for the %D macro on the secure 
connect failed error page when an SSL handshake with the origin server fails.
The default %D text is "Handshake with SSL server failed: XYZ" where XYZ is the
corresponding error string/description returned by OpenSSL if there is any.

This is a Measurement Factory project.

=== modified file 'errors/templates/error-details.txt'
--- errors/templates/error-details.txt	2011-06-24 01:29:53 +0000
+++ errors/templates/error-details.txt	2011-10-28 16:27:41 +0000
@@ -1,20 +1,24 @@
+name: SQUID_ERR_SSL_HANDSHAKE
+detail: "%ssl_error_descr: %ssl_lib_error"
+descr: "Handshake with SSL server failed"
+
 name: SQUID_X509_V_ERR_DOMAIN_MISMATCH
 detail: "%ssl_error_descr: %ssl_subject"
 descr: "Certificate does not match domainname"
 
 name: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT
 detail: "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name"
 descr: "Unable to get issuer certificate"
 
 name: X509_V_ERR_UNABLE_TO_GET_CRL
 detail: "%ssl_error_descr: %ssl_subject"
 descr: "Unable to get certificate CRL"
 
 name: X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE
 detail: "%ssl_error_descr: %ssl_subject"
 descr: "Unable to decrypt certificate's signature"
 
 name: X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE
 detail: "%ssl_error_descr: %ssl_subject"
 descr: "Unable to decrypt CRL's signature"
 

=== modified file 'src/forward.cc'
--- src/forward.cc	2011-10-10 11:54:04 +0000
+++ src/forward.cc	2011-11-01 21:38:28 +0000
@@ -564,76 +564,96 @@
 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_SSL
 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;
 
-        default:
+        case SSL_ERROR_SSL:
+        case SSL_ERROR_SYSCALL:
+            ssl_lib_error = ERR_get_error();
             debugs(81, 1, "fwdNegotiateSSL: Error negotiating SSL connection on FD " << fd <<
-                   ": " << ERR_error_string(ERR_get_error(), NULL) << " (" << ssl_error <<
+                   ": " << ERR_error_string(ssl_lib_error, NULL) << " (" << ssl_error <<
                    "/" << ret << "/" << errno << ")");
-            ErrorState *const anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
-#ifdef EPROTO
 
-            anErr->xerrno = EPROTO;
-#else
+            // 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;
 
-            anErr->xerrno = EACCES;
-#endif
+            // falling through to complete error handling
+
+        default:
+            ErrorState *const anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
+            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 {
+                // clientCert can be be NULL
+                X509 *client_cert = SSL_get_peer_certificate(ssl);
+                anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, client_cert);
+                if (client_cert)
+                    X509_free(client_cert);
+            }
+
+            if (ssl_lib_error != SSL_ERROR_NONE)
+                anErr->detail->setLibError(ssl_lib_error);
 
             fail(anErr);
 
             if (serverConnection()->getPeer()) {
                 peerConnectFailed(serverConnection()->getPeer());
             }
 
             serverConn->close();
             return;
         }
     }
 
     if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
         if (serverConnection()->getPeer()->sslSession)
             SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
 
         serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
     }
 
     dispatch();

=== modified file 'src/ssl/ErrorDetail.cc'
--- src/ssl/ErrorDetail.cc	2011-06-23 00:23:48 +0000
+++ src/ssl/ErrorDetail.cc	2011-11-01 15:47:38 +0000
@@ -1,38 +1,40 @@
 #include "squid.h"
 #include "errorpage.h"
 #include "ssl/ErrorDetail.h"
 #if HAVE_MAP
 #include <map>
 #endif
 
 struct SslErrorEntry {
     Ssl::ssl_error_t value;
     const char *name;
 };
 
 static const char *SslErrorDetailDefaultStr = "SSL certificate validation error (%err_name): %ssl_subject";
 //Use std::map to optimize search
 typedef std::map<Ssl::ssl_error_t, const SslErrorEntry *> SslErrors;
 SslErrors TheSslErrors;
 
 static SslErrorEntry TheSslErrorArray[] = {
+    {SQUID_ERR_SSL_HANDSHAKE,
+     "SQUID_ERR_SSL_HANDSHAKE"},
     {SQUID_X509_V_ERR_DOMAIN_MISMATCH,
         "SQUID_X509_V_ERR_DOMAIN_MISMATCH"},
     {X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
      "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"},
     {X509_V_ERR_UNABLE_TO_GET_CRL,
      "X509_V_ERR_UNABLE_TO_GET_CRL"},
     {X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE,
      "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"},
     {X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE,
      "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"},
     {X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY,
      "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"},
     {X509_V_ERR_CERT_SIGNATURE_FAILURE,
      "X509_V_ERR_CERT_SIGNATURE_FAILURE"},
     {X509_V_ERR_CRL_SIGNATURE_FAILURE,
      "X509_V_ERR_CRL_SIGNATURE_FAILURE"},
     {X509_V_ERR_CERT_NOT_YET_VALID,
      "X509_V_ERR_CERT_NOT_YET_VALID"},
     {X509_V_ERR_CERT_HAS_EXPIRED,
      "X509_V_ERR_CERT_HAS_EXPIRED"},
@@ -132,40 +134,41 @@
     if (it != TheSslErrors.end())
         return it->second->name;
 
     return NULL;
 }
 
 const char *
 Ssl::GetErrorDescr(Ssl::ssl_error_t value)
 {
     return ErrorDetailsManager::GetInstance().getDefaultErrorDescr(value);
 }
 
 Ssl::ErrorDetail::err_frm_code Ssl::ErrorDetail::ErrorFormatingCodes[] = {
     {"ssl_subject", &Ssl::ErrorDetail::subject},
     {"ssl_ca_name", &Ssl::ErrorDetail::ca_name},
     {"ssl_cn", &Ssl::ErrorDetail::cn},
     {"ssl_notbefore", &Ssl::ErrorDetail::notbefore},
     {"ssl_notafter", &Ssl::ErrorDetail::notafter},
     {"err_name", &Ssl::ErrorDetail::err_code},
     {"ssl_error_descr", &Ssl::ErrorDetail::err_descr},
+    {"ssl_lib_error", &Ssl::ErrorDetail::err_lib_error},
     {NULL,NULL}
 };
 
 /**
  * The subject of the current certification in text form
  */
 const char  *Ssl::ErrorDetail::subject() const
 {
     if (!peer_cert)
         return "[Not available]";
 
     static char tmpBuffer[256]; // A temporary buffer
     X509_NAME_oneline(X509_get_subject_name(peer_cert.get()), tmpBuffer,
                       sizeof(tmpBuffer));
     return tmpBuffer;
 }
 
 // helper function to be used with Ssl::matchX509CommonNames
 static int copy_cn(void *check_data,  ASN1_STRING *cn_data)
 {
@@ -250,50 +253,59 @@
 
     if (!err) {
         snprintf(tmpBuffer, 64, "%d", (int)error_no);
         err = tmpBuffer;
     }
     return err;
 }
 
 /**
  * A short description of the error_no
  */
 const char *Ssl::ErrorDetail::err_descr() const
 {
     if (error_no == SSL_ERROR_NONE)
         return "[No Error]";
     if (const char *err = detailEntry.descr.termedBuf())
         return err;
     return "[Not available]";
 }
 
+const char *Ssl::ErrorDetail::err_lib_error() const
+{
+    if (lib_error_no != SSL_ERROR_NONE)
+        return ERR_error_string(lib_error_no, NULL);
+    else
+        return "[No Error]";
+}
+
 /**
  * It converts the code to a string value. Currently the following
  * formating codes are supported:
- * %err_name: The name of the SSL error
+ * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*)
  * %ssl_error_descr: A short description of the SSL error
  * %ssl_cn: The comma-separated list of common and alternate names
  * %ssl_subject: The certificate subject
  * %ssl_ca_name: The certificate issuer name
  * %ssl_notbefore: The certificate "not before" field
  * %ssl_notafter: The certificate "not after" field
+ * %ssl_lib_error: human-readable low-level error string by ERR_error_string(3SSL)
  \retval  the length of the code (the number of characters will be replaced by value)
 */
 int Ssl::ErrorDetail::convert(const char *code, const char **value) const
 {
     *value = "-";
     for (int i=0; ErrorFormatingCodes[i].code!=NULL; i++) {
         const int len = strlen(ErrorFormatingCodes[i].code);
         if (strncmp(code,ErrorFormatingCodes[i].code, len)==0) {
             ErrorDetail::fmt_action_t action  = ErrorFormatingCodes[i].fmt_action;
             *value = (this->*action)();
             return len;
         }
     }
     return 0;
 }
 
 /**
  * It uses the convert method to build the string errDetailStr using
  * a template message for the current SSL error. The template messages
  * can also contain normal error pages formating codes.
@@ -320,37 +332,41 @@
             errDetailStr.append(t);
         else
             errDetailStr.append("%");
         s = p + code_len;
     }
     errDetailStr.append(s, strlen(s));
 }
 
 const String &Ssl::ErrorDetail::toString() const
 {
     if (!errDetailStr.defined())
         buildDetail();
     return errDetailStr;
 }
 
 /* We may do not want to use X509_dup but instead
    internal SSL locking:
    CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509);
    peer_cert.reset(cert);
 */
-Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert): error_no (err_no)
+Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert): error_no (err_no), lib_error_no(SSL_ERROR_NONE)
 {
-    peer_cert.reset(X509_dup(cert));
+    if (cert)
+        peer_cert.reset(X509_dup(cert));
+
     detailEntry.error_no = SSL_ERROR_NONE;
 }
 
 Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail)
 {
     error_no = anErrDetail.error_no;
     request = anErrDetail.request;
 
     if (anErrDetail.peer_cert.get()) {
         peer_cert.reset(X509_dup(anErrDetail.peer_cert.get()));
     }
 
     detailEntry = anErrDetail.detailEntry;
+
+    lib_error_no = anErrDetail.lib_error_no;
 }

=== modified file 'src/ssl/ErrorDetail.h'
--- src/ssl/ErrorDetail.h	2011-10-27 15:22:21 +0000
+++ src/ssl/ErrorDetail.h	2011-11-01 15:45:43 +0000
@@ -35,54 +35,58 @@
 /**
    \ingroup ServerProtocolSSLAPI
  * A short description of the SSL error "value"
  */
 const char *GetErrorDescr(ssl_error_t value);
 
 /**
    \ingroup ServerProtocolSSLAPI
  * Used to pass SSL error details to the error pages returned to the
  * end user.
  */
 class ErrorDetail
 {
 public:
     ErrorDetail(ssl_error_t err_no, X509 *cert);
     ErrorDetail(ErrorDetail const &);
     const String &toString() const;  ///< An error detail string to embed in squid error pages
     void useRequest(HttpRequest *aRequest) { if (aRequest != NULL) request = aRequest;}
     /// The error name to embed in squid error pages
     const char *errorName() const {return err_code();}
+    ///Sets the low-level error returned by OpenSSL ERR_get_error()
+    void setLibError(unsigned long lib_err_no) {lib_error_no = lib_err_no;}
 
 private:
     typedef const char * (ErrorDetail::*fmt_action_t)() const;
     /**
      * Holds a formating code and its conversion method
      */
     class err_frm_code
     {
     public:
         const char *code;             ///< The formating code
         fmt_action_t fmt_action; ///< A pointer to the conversion method
     };
     static err_frm_code  ErrorFormatingCodes[]; ///< The supported formating codes
 
     const char *subject() const;
     const char *ca_name() const;
     const char *cn() const;
     const char *notbefore() const;
     const char *notafter() const;
     const char *err_code() const;
     const char *err_descr() const;
+    const char *err_lib_error() const;
 
     int convert(const char *code, const char **value) const;
     void buildDetail() const;
 
     mutable String errDetailStr; ///< Caches the error detail message
     ssl_error_t error_no;   ///< The error code
+    unsigned long lib_error_no; ///< low-level error returned by OpenSSL ERR_get_error(3SSL)
     X509_Pointer peer_cert; ///< A pointer to the peer certificate
     mutable ErrorDetailEntry detailEntry;
     HttpRequest::Pointer request;
 };
 
 }//namespace Ssl
 #endif

=== modified file 'src/ssl/support.h'
--- src/ssl/support.h	2011-10-27 15:27:25 +0000
+++ src/ssl/support.h	2011-10-28 17:39:05 +0000
@@ -39,43 +39,44 @@
 
 #if HAVE_OPENSSL_SSL_H
 #include <openssl/ssl.h>
 #endif
 #if HAVE_OPENSSL_X509V3_H
 #include <openssl/x509v3.h>
 #endif
 #if HAVE_OPENSSL_ERR_H
 #include <openssl/err.h>
 #endif
 #if HAVE_OPENSSL_ENGINE_H
 #include <openssl/engine.h>
 #endif
 
 /**
  \defgroup ServerProtocolSSLAPI Server-Side SSL API
  \ingroup ServerProtocol
  */
 
 // Custom SSL errors; assumes all official errors are positive
+#define SQUID_ERR_SSL_HANDSHAKE -2
 #define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1
 // All SSL errors range: from smallest (negative) custom to largest SSL error
-#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_DOMAIN_MISMATCH
+#define SQUID_SSL_ERROR_MIN SQUID_ERR_SSL_HANDSHAKE
 #define SQUID_SSL_ERROR_MAX INT_MAX
 
 namespace Ssl
 {
 /// Squid defined error code (<0),  an error code returned by SSL X509 api, or SSL_ERROR_NONE
 typedef int ssl_error_t;
 } //namespace Ssl
 
 /// \ingroup ServerProtocolSSLAPI
 SSL_CTX *sslCreateServerContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *clientCA, const char *CAfile, const char *CApath, const char *CRLfile, const char *dhpath, const char *context);
 
 /// \ingroup ServerProtocolSSLAPI
 SSL_CTX *sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile);
 
 /// \ingroup ServerProtocolSSLAPI
 int ssl_read_method(int, char *, int);
 
 /// \ingroup ServerProtocolSSLAPI
 int ssl_write_method(int, const char *, int);
 

Reply via email to