]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Report ERR_SECURE_CONNECT_FAIL details to the user via a new error detail API.
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Mon, 13 Dec 2010 23:02:52 +0000 (01:02 +0200)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Mon, 13 Dec 2010 23:02:52 +0000 (01:02 +0200)
Currently, the ERR_SECURE_CONNECT_FAIL response contains no usable error
information. Moreover, there is no interface to pass SSL error information
to the response generation code.

This patch adds an interface to allow Squid error responses to contain detailed
information about SSL certificate verification failure. For example, the error
message may contain the following text:
 "Server Certificate Verification Failed: Certificate Common Name
  (www.lufthansa.com) does not match the host name you are connecting to
  (www.lufthansa.de)."

This is a Measurement Factory project.

Change details:
--------------------

- errorpage.cc/.h: The error page now supports the '%D'  formating code to
  display the detail string passed by modules. The detail strings passed by
  modules can contain error page formating codes. Currently only SSL detail
  errors messages are supported.

- A new class Ssl::ErrorDetail defined in ssl/ErrorDetail.[cc,h]
  The Ssl::ErrorDetail objects passed to the SSL verification callback functions
  (sl_verify_cb callback function defined in support.cc) and filled with error
  detail data (error_no and a pointer to the X509 Certificate) in the case of
  an error and passed back to the forward.cc code.

- The Ssl::ErrorDetail class internally uses (hard coded) templates and
  formating codes to allow supporting multiple languages and adding easily
   new features

Other changes:
-------------------

- errorpage.cc/.h: The BuildContent method split to BuildContent and ConvertText
  method. The second method does the real conversion from a given text template
  to output. It is used now to allow formating the detail strings passed with
  %D.

 - sslparseErrorString moved to ssl/ErrorDetail.cc file and renamed to
   Ssl::parseErrorString

 - sslFindErrorString moved to ssl/ErrorDetail.cc file and renamed to
   Ssl::getErrorName

 - The ssl_error_t typedef definition moved from ssl/support.h to
   ssl/ErrorDetail.h and renamed to Ssl::error_t

src/acl/SslErrorData.cc
src/acl/SslErrorData.h
src/errorpage.cc
src/errorpage.h
src/forward.cc
src/globals.h
src/ssl/ErrorDetail.cc [new file with mode: 0644]
src/ssl/ErrorDetail.h [new file with mode: 0644]
src/ssl/Makefile.am
src/ssl/support.cc
src/ssl/support.h

index 3de564c1c8852c9a77f4cd47916765ffe2c47260..93381a61773aad132cfd33cce878b32d38f32a80 100644 (file)
@@ -22,7 +22,7 @@ ACLSslErrorData::~ACLSslErrorData()
 }
 
 bool
-ACLSslErrorData::match(ssl_error_t toFind)
+ACLSslErrorData::match(Ssl::error_t toFind)
 {
     return values->findAndTune (toFind);
 }
@@ -30,17 +30,17 @@ ACLSslErrorData::match(ssl_error_t toFind)
 /* explicit instantiation required for some systems */
 /** \cond AUTODOCS-IGNORE */
 // AYJ: 2009-05-20 : Removing. clashes with template <int> instantiation for other ACLs.
-// template cbdata_type CbDataList<ssl_error_t>::CBDATA_CbDataList;
+// template cbdata_type CbDataList<Ssl::error_t>::CBDATA_CbDataList;
 /** \endcond */
 
 wordlist *
 ACLSslErrorData::dump()
 {
     wordlist *W = NULL;
-    CbDataList<ssl_error_t> *data = values;
+    CbDataList<Ssl::error_t> *data = values;
 
     while (data != NULL) {
-        wordlistAdd(&W, sslFindErrorString(data->element));
+        wordlistAdd(&W, Ssl::getErrorName(data->element));
         data = data->next;
     }
 
@@ -50,12 +50,12 @@ ACLSslErrorData::dump()
 void
 ACLSslErrorData::parse()
 {
-    CbDataList<ssl_error_t> **Tail;
+    CbDataList<Ssl::error_t> **Tail;
     char *t = NULL;
 
     for (Tail = &values; *Tail; Tail = &((*Tail)->next));
     while ((t = strtokFile())) {
-        CbDataList<ssl_error_t> *q = new CbDataList<ssl_error_t>(sslParseErrorString(t));
+        CbDataList<Ssl::error_t> *q = new CbDataList<Ssl::error_t>(Ssl::parseErrorString(t));
         *(Tail) = q;
         Tail = &q->next;
     }
@@ -67,7 +67,7 @@ ACLSslErrorData::empty() const
     return values == NULL;
 }
 
-ACLData<ssl_error_t> *
+ACLData<Ssl::error_t> *
 ACLSslErrorData::clone() const
 {
     /* Splay trees don't clone yet. */
index fc0afff5498900a9beaa8c700c131cd0c835ff14..d2974921a9c2df82f8097b742268a50528ef5fe5 100644 (file)
@@ -9,8 +9,9 @@
 #include "acl/Data.h"
 #include "CbDataList.h"
 #include "ssl/support.h"
+#include "ssl/ErrorDetail.h"
 
-class ACLSslErrorData : public ACLData<ssl_error_t>
+class ACLSslErrorData : public ACLData<Ssl::error_t>
 {
 
 public:
@@ -20,13 +21,13 @@ public:
     ACLSslErrorData(ACLSslErrorData const &);
     ACLSslErrorData &operator= (ACLSslErrorData const &);
     virtual ~ACLSslErrorData();
-    bool match(ssl_error_t);
+    bool match(Ssl::error_t);
     wordlist *dump();
     void parse();
     bool empty() const;
-    virtual ACLData<ssl_error_t> *clone() const;
+    virtual ACLData<Ssl::error_t> *clone() const;
 
-    CbDataList<ssl_error_t> *values;
+    CbDataList<Ssl::error_t> *values;
 };
 
 MEMPROXY_CLASS_INLINE(ACLSslErrorData);
index af4edeedc5ad046db46b7787672f08be55949c87..ccdbfccdb82dcbb0243343a1eb2e43d02e26e88b 100644 (file)
@@ -513,6 +513,9 @@ errorStateFree(ErrorState * err)
     if (err->err_language != Config.errorDefaultLanguage)
 #endif
         safe_free(err->err_language);
+#if USE_SSL
+    delete err->detail;
+#endif
     cbdataFree(err);
 }
 
@@ -602,7 +605,7 @@ ErrorState::Dump(MemBuf * mb)
 #define CVT_BUF_SZ 512
 
 const char *
-ErrorState::Convert(char token, bool building_deny_info_url)
+ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion)
 {
     static MemBuf mb;
     const char *p = NULL;      /* takes priority over mb if set */
@@ -631,6 +634,22 @@ ErrorState::Convert(char token, bool building_deny_info_url)
         p = errorPageName(type);
         break;
 
+    case 'D':
+        if (!allowRecursion)
+            p = "%D";  // if recursion is not allowed, do not convert
+#if USE_SSL
+        // currently only SSL error details implemented
+        else if (detail) {
+            const String &errDetail = detail->toString();
+            MemBuf *detail_mb  = ConvertText(errDetail.termedBuf(), false);
+            mb.append(detail_mb->content(), detail_mb->contentSize());
+            delete detail_mb;
+            do_quote = 0;
+        } else
+#endif
+            mb.Printf("[No Error Detail]");
+        break;
+
     case 'e':
         mb.Printf("%d", xerrno);
         break;
@@ -898,7 +917,7 @@ ErrorState::DenyInfoLocation(const char *name, HttpRequest *aRequest, MemBuf &re
 
     while ((p = strchr(m, '%'))) {
         result.append(m, p - m);       /* copy */
-        t = Convert(*++p, true);       /* convert */
+        t = Convert(*++p, true, true);       /* convert */
         result.Printf("%s", t);        /* copy */
         m = p + 1;                     /* advance */
     }
@@ -976,10 +995,7 @@ ErrorState::BuildHttpReply()
 MemBuf *
 ErrorState::BuildContent()
 {
-    MemBuf *content = new MemBuf;
     const char *m = NULL;
-    const char *p;
-    const char *t;
 
     assert(page_id > ERR_NONE && page_id < error_page_count);
 
@@ -1096,12 +1112,20 @@ ErrorState::BuildContent()
         debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
     }
 
+    return ConvertText(m, true);
+}
+
+MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion)
+{
+    MemBuf *content = new MemBuf;
+    const char *p;
+    const char *m = text;
     assert(m);
     content->init();
 
     while ((p = strchr(m, '%'))) {
         content->append(m, p - m);     /* copy */
-        t = Convert(*++p, false);      /* convert */
+        const char *t = Convert(*++p, false, allowRecursion);  /* convert */
         content->Printf("%s", t);      /* copy */
         m = p + 1;                     /* advance */
     }
index 74a30b6efc504a8a3f44a1c57c47ad95763baf65..e3ea32cfa304febd03a282df3cb767ee13b1e081 100644 (file)
@@ -38,6 +38,9 @@
 #include "auth/UserRequest.h"
 #include "cbdata.h"
 #include "ip/Address.h"
+#if USE_SSL
+#include "ssl/ErrorDetail.h"
+#endif
 
 /**
  \defgroup ErrorPageAPI Error Pages API
@@ -49,6 +52,7 @@
    B - URL with FTP %2f hack                    x
    c - Squid error code                         x
    d - seconds elapsed since request received   x
+   D - Error details                            x
    e - errno                                    x
    E - strerror()                               x
    f - FTP request line                         x
@@ -98,6 +102,14 @@ private:
      */
     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
+     */
+    MemBuf *ConvertText(const char *text, bool allowRecursion);
+
     /**
      * Generates the Location: header value for a deny_info error page
      * to be used for this error.
@@ -112,8 +124,9 @@ private:
      *
      * \param token                    The token following % which need to be converted
      * \param building_deny_info_url   Perform special deny_info actions, such as URL-encoding and token skipping.
+     * \ allowRecursion   True if the codes which do recursions should converted
      */
-    const char *Convert(char token, bool building_deny_info_url);
+    const char *Convert(char token, bool building_deny_info_url, bool allowRecursion);
 
     /**
      * CacheManager / Debug dump of the ErrorState object.
@@ -155,6 +168,9 @@ public:
     char *request_hdrs;
     char *err_msg; /* Preformatted error message from the cache */
 
+#if USE_SSL
+    Ssl::ErrorDetail *detail;
+#endif
 private:
     CBDATA_CLASS2(ErrorState);
 };
index bfa6d4792f138f0f86737388c4307d616f0dd85a..f20bb209a775d6bbf9881610c9587d0e5f16b558 100644 (file)
@@ -53,6 +53,7 @@
 #include "mgr/Registration.h"
 #if USE_SSL
 #include "ssl/support.h"
+#include "ssl/ErrorDetail.h"
 #endif
 
 static PSC fwdStartCompleteWrapper;
@@ -604,6 +605,14 @@ FwdState::negotiateSSL(int fd)
             anErr->xerrno = EACCES;
 #endif
 
+            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);
+            }
+
             fail(anErr);
 
             if (fs->_peer) {
index 0ac3da8aeb94b7a84bdcd5e5b6839c7ee7b40e39..3fc2007e97daacb520adfbcfc031438c39893f99 100644 (file)
@@ -165,6 +165,7 @@ extern "C" {
     extern int ssl_ex_index_server;    /* -1 */
     extern int ssl_ctx_ex_index_dont_verify_domain; /* -1 */
     extern int ssl_ex_index_cert_error_check;  /* -1 */
+    extern int ssl_ex_index_ssl_error_detail;      /* -1 */
 
     extern const char *external_acl_message;      /* NULL */
     extern int opt_send_signal;        /* -1 */
diff --git a/src/ssl/ErrorDetail.cc b/src/ssl/ErrorDetail.cc
new file mode 100644 (file)
index 0000000..ac0d5a5
--- /dev/null
@@ -0,0 +1,262 @@
+#include "squid.h"
+#include "ssl/ErrorDetail.h"
+
+struct SslErrorDetailEntry {
+    Ssl::error_t value;
+    const char *name;
+    const char *detail;
+};
+
+// TODO: optimize by replacing with std::map or similar
+static SslErrorDetailEntry TheSslDetailMap[] = {
+    {  SQUID_X509_V_ERR_DOMAIN_MISMATCH, 
+       "SQUID_X509_V_ERR_DOMAIN_MISMATCH", 
+       "%err_name: The hostname you are connecting to (%H),  does not match any of the Certificate valid names: %ssl_cn"},
+    { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT,
+      "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT",
+      "%err_name: SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" },
+    { X509_V_ERR_CERT_NOT_YET_VALID,
+      "X509_V_ERR_CERT_NOT_YET_VALID",
+      "%err_name: SSL Certficate is not valid before: %ssl_notbefore" },
+    { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD,
+      "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD",
+      "%err_name: SSL Certificate has invalid start date (the 'not before' field): %subject" },
+    { X509_V_ERR_CERT_HAS_EXPIRED,
+      "X509_V_ERR_CERT_HAS_EXPIRED",
+      "%err_name: SSL Certificate expired on %ssl_notafter" },
+    { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD,
+      "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD",
+      "%err_name: SSL Certificate has invalid expiration date (the 'not after' field): %ssl_subject" },
+    {X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+     "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT",
+     "%err_name: Self-signed SSL Certificate: %ssl_subject"},
+    { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+      "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
+      "%err_name: SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" },
+    { SSL_ERROR_NONE, "SSL_ERROR_NONE", "%err_name: No error" },
+    {SSL_ERROR_NONE, NULL, NULL }
+};
+
+Ssl::error_t
+Ssl::parseErrorString(const char *name)
+{
+    assert(name);
+
+    for (int i = 0; TheSslDetailMap[i].name; ++i) {
+        if (strcmp(name, TheSslDetailMap[i].name) == 0)
+            return TheSslDetailMap[i].value;
+    }
+
+    if (xisdigit(*name)) {
+        const long int value = strtol(name, NULL, 0);
+        if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX)
+            return value;
+        fatalf("Too small or too bug SSL error code '%s'", name);
+    }
+
+    fatalf("Unknown SSL error name '%s'", name);
+    return SSL_ERROR_SSL; // not reached
+}
+
+const char *
+Ssl::getErrorName(Ssl::error_t value)
+{
+
+    for (int i = 0; TheSslDetailMap[i].name; ++i) {
+        if (TheSslDetailMap[i].value == value)
+            return TheSslDetailMap[i].name;
+    }
+
+    return NULL;
+}
+
+static const char *getErrorDetail(Ssl::error_t value)
+{
+    for (int i = 0; TheSslDetailMap[i].name; ++i) {
+        if (TheSslDetailMap[i].value == value)
+            return TheSslDetailMap[i].detail;
+    }
+
+    return NULL;
+}
+
+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},
+    {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)
+{
+    String *str = (String *)check_data;
+    if (!str) // no data? abort
+        return 0;
+    if (str->defined())
+        str->append(", ");
+    str->append((const char *)cn_data->data, cn_data->length);
+    return 1;
+}
+
+/**
+ * The list with certificates cn and alternate names
+ */
+const char *Ssl::ErrorDetail::cn() const
+{
+    if (!peer_cert)
+        return "[Not available]";
+
+    static String tmpStr;  ///< A temporary string buffer
+    tmpStr.clean();
+    Ssl::matchX509CommonNames(peer_cert.get(), &tmpStr, copy_cn);
+    return tmpStr.termedBuf();
+}
+
+/**
+ * The issuer name
+ */
+const char *Ssl::ErrorDetail::ca_name() const
+{
+    if (!peer_cert)
+        return "[Not available]";
+
+    static char tmpBuffer[256]; // A temporary buffer
+    X509_NAME_oneline(X509_get_issuer_name(peer_cert.get()), tmpBuffer, sizeof(tmpBuffer));
+    return tmpBuffer;
+}
+
+/**
+ * The certificate "not before" field
+ */
+const char *Ssl::ErrorDetail::notbefore() const
+{
+    if (!peer_cert)
+        return "[Not available]";
+
+    static char tmpBuffer[256]; // A temporary buffer
+    ASN1_UTCTIME * tm = X509_get_notBefore(peer_cert.get());
+    Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
+    return tmpBuffer;
+}
+
+/**
+ * The certificate "not after" field
+ */
+const char *Ssl::ErrorDetail::notafter() const
+{
+    if (!peer_cert)
+        return "[Not available]";
+
+    static char tmpBuffer[256]; // A temporary buffer
+    ASN1_UTCTIME * tm = X509_get_notAfter(peer_cert.get());
+    Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer));
+    return tmpBuffer;
+}
+
+/**
+ * The string representation of the error_no
+ */
+const char *Ssl::ErrorDetail::err_code() const
+{
+    const char *err = getErrorName(error_no);
+    if (!err)
+        return "[Not available]";
+    return err;
+}
+
+/**
+ * It converts the code to a string value. Currently the following
+ * formating codes are supported:
+ * %err_name: The name 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
+ \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.
+ * Currently the error template messages are hard-coded
+ */
+void Ssl::ErrorDetail::buildDetail() const
+{
+    char const *s = getErrorDetail(error_no);
+    char const *p;
+    char const *t;
+    int code_len = 0;
+
+    if (!s)  //May be add a default detail string?
+        return;
+
+    while ((p = strchr(s, '%'))) {
+        errDetailStr.append(s, p - s);
+        code_len = convert(++p, &t);
+        if (code_len)
+            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( error_t err_no, X509 *cert): error_no (err_no)
+{
+    peer_cert.reset(X509_dup(cert));
+}
+
+Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail)
+{
+    error_no = anErrDetail.error_no;
+    if (anErrDetail.peer_cert.get()) {
+        peer_cert.reset(X509_dup(anErrDetail.peer_cert.get()));
+    }
+}
diff --git a/src/ssl/ErrorDetail.h b/src/ssl/ErrorDetail.h
new file mode 100644 (file)
index 0000000..7a2d847
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef _SQUID_SSL_ERROR_DETAIL_H
+#define _SQUID_SSL_ERROR_DETAIL_H
+
+#include "err_detail_type.h"
+#include "ssl/support.h"
+#include "ssl/gadgets.h"
+
+#if HAVE_OPENSSL_SSL_H
+#include <openssl/ssl.h>
+#endif
+
+// Custom SSL errors; assumes all official errors are positive
+#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_MAX INT_MAX
+
+namespace Ssl 
+{
+    /// Squid defined error code (<0),  an error code returned by SSL X509 api, or SSL_ERROR_NONE
+    typedef int error_t;
+
+/**
+   \ingroup ServerProtocolSSLAPI
+ * The error_t representation of the error described by "name".
+ */
+error_t parseErrorString(const char *name);
+
+/**
+   \ingroup ServerProtocolSSLAPI
+ * The string representation of the SSL error "value"
+ */
+const char *getErrorName(error_t value); 
+
+/**
+   \ingroup ServerProtocolSSLAPI
+ * Used to pass SSL error details to the error pages returned to the
+ * end user.
+ */
+class ErrorDetail {
+public:
+    ErrorDetail(error_t err_no, X509 *cert);
+    ErrorDetail(ErrorDetail const &);
+    const String &toString() const;  ///< An error detail string to embed in squid error pages
+
+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;
+
+    int convert(const char *code, const char **value) const;
+    void buildDetail() const;
+    
+    mutable String errDetailStr; ///< Caches the error detail message
+    error_t error_no;   ///< The error code
+    X509_Pointer peer_cert; ///< A pointer to the peer certificate 
+};
+
+}//namespace Ssl
+#endif
index 8aad6f469846a267f782bcc0514868acc52d05f3..6eb6756a662206a6c36af504fe79512c457d7a5d 100644 (file)
@@ -20,11 +20,13 @@ libsslsquid_la_SOURCES = \
        context_storage.cc \
        context_storage.h \
        Config.cc \
-       Config.h
+       Config.h \
+       ErrorDetail.cc \
+       ErrorDetail.h \
+       support.cc \
+       support.h
 
 libsslutil_la_SOURCES = \
-       support.cc \
-       support.h \
        gadgets.cc \
        gadgets.h \
        crtd_message.cc \
index 0f51098606948f0b2210775fd87bd9ea7247ca6b..0feeab361e2cbc475b7460400884f9e36e7cc6f9 100644 (file)
@@ -42,6 +42,7 @@
 
 #include "fde.h"
 #include "acl/FilledChecklist.h"
+#include "ssl/ErrorDetail.h"
 #include "ssl/support.h"
 #include "ssl/gadgets.h"
 
@@ -137,6 +138,68 @@ ssl_temp_rsa_cb(SSL * ssl, int anInt, int keylen)
     return rsa;
 }
 
+int Ssl::asn1timeToString(ASN1_TIME *tm, char *buf, int len)
+{
+    BIO *bio;
+    int write = 0;
+    bio = BIO_new(BIO_s_mem());
+    if (bio) {
+       if (ASN1_TIME_print(bio, tm))
+           write = BIO_read(bio, buf, len-1);
+       BIO_free(bio);
+    }
+    buf[write]='\0';
+    return write;
+}
+
+int Ssl::matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data,  ASN1_STRING *cn_data))
+{
+    assert(peer_cert);
+
+    X509_NAME *name = X509_get_subject_name(peer_cert);
+
+    for (int i = X509_NAME_get_index_by_NID(name, NID_commonName, -1); i >= 0; i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) {
+       
+        ASN1_STRING *cn_data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
+
+        if ( (*check_func)(check_data, cn_data) == 0)
+            return 1;
+    }
+
+    STACK_OF(GENERAL_NAME) * altnames;
+    altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL);
+
+    if (altnames) {
+        int numalts = sk_GENERAL_NAME_num(altnames);
+        for (int i = 0; i < numalts; i++) {
+            const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
+            if (check->type != GEN_DNS) {
+                continue;
+            }
+            ASN1_STRING *cn_data = check->d.dNSName;
+            
+            if ( (*check_func)(check_data, cn_data) == 0)
+                return 1;
+        }
+        sk_GENERAL_NAME_pop_free(altnames, GENERAL_NAME_free);
+    }
+    return 0;
+}
+
+static int check_domain( void *check_data, ASN1_STRING *cn_data)
+{
+    char cn[1024];
+    const char *server = (const char *)check_data;
+
+    if (cn_data->length > (int)sizeof(cn) - 1) { 
+        return 1; //if does not fit our buffer just ignore
+    }
+    memcpy(cn, cn_data->data, cn_data->length);
+    cn[cn_data->length] = '\0';
+    debugs(83, 4, "Verifying server domain " << server << " to certificate name/subjectAltName " << cn);
+    return matchDomainName(server, cn[0] == '*' ? cn + 1 : cn);
+}
+
 /// \ingroup ServerProtocolSSLInternal
 static int
 ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
@@ -148,6 +211,7 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
     void *dont_verify_domain = SSL_CTX_get_ex_data(sslctx, ssl_ctx_ex_index_dont_verify_domain);
     ACLChecklist *check = (ACLChecklist*)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check);
     X509 *peer_cert = ctx->cert;
+    Ssl::error_t error_no = SSL_ERROR_NONE;
 
     X509_NAME_oneline(X509_get_subject_name(peer_cert), buffer,
                       sizeof(buffer));
@@ -155,66 +219,23 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
     if (ok) {
         debugs(83, 5, "SSL Certificate signature OK: " << buffer);
 
-        if (server) {
-            int i;
-            int found = 0;
-            char cn[1024];
-
-            STACK_OF(GENERAL_NAME) * altnames;
-            altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL);
-            if (altnames) {
-                int numalts = sk_GENERAL_NAME_num(altnames);
-                debugs(83, 3, "Verifying server domain " << server << " to certificate subjectAltName");
-                for (i = 0; i < numalts; i++) {
-                    const GENERAL_NAME *check = sk_GENERAL_NAME_value(altnames, i);
-                    if (check->type != GEN_DNS) {
-                        continue;
-                    }
-                    ASN1_STRING *data = check->d.dNSName;
-                    if (data->length > (int)sizeof(cn) - 1) {
-                        continue;
-                    }
-                    memcpy(cn, data->data, data->length);
-                    cn[data->length] = '\0';
-                    debugs(83, 4, "Verifying server domain " << server << " to certificate name " << cn);
-                    if (matchDomainName(server, cn[0] == '*' ? cn + 1 : cn) == 0) {
-                        found = 1;
-                        break;
-                    }
-                }
-            }
-
-            X509_NAME *name = X509_get_subject_name(peer_cert);
-            debugs(83, 3, "Verifying server domain " << server << " to certificate dn " << buffer);
-
-            for (i = X509_NAME_get_index_by_NID(name, NID_commonName, -1); i >= 0; i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) {
-                ASN1_STRING *data = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, i));
-
-                if (data->length > (int)sizeof(cn) - 1)
-                    continue;
-
-                memcpy(cn, data->data, data->length);
-
-                cn[data->length] = '\0';
-
-                debugs(83, 4, "Verifying server domain " << server << " to certificate cn " << cn);
-
-                if (matchDomainName(server, cn[0] == '*' ? cn + 1 : cn) == 0) {
-                    found = 1;
-                    break;
-                }
-            }
+        if (server) {            
+            int found = Ssl::matchX509CommonNames(peer_cert, (void *)server, check_domain);
 
             if (!found) {
                 debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << buffer << " does not match domainname " << server);
                 ok = 0;
+                error_no = SQUID_X509_V_ERR_DOMAIN_MISMATCH;
+
                 if (check)
                     Filled(check)->ssl_error = SQUID_X509_V_ERR_DOMAIN_MISMATCH;
             }
         }
     } else {
+        error_no = ctx->error;
         switch (ctx->error) {
 
+        case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
         case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
             debugs(83, 5, "SSL Certficate error: CA not known: " << buffer);
             break;
@@ -237,6 +258,10 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
             debugs(83, 5, "SSL Certificate has invalid \'not after\' field: " << buffer);
             break;
 
+        case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+            debugs(83, 5, "SSL Certificate is self signed: " << buffer);
+            break;
+            
         default:
             debugs(83, 1, "SSL unknown certificate error " << ctx->error << " in " << buffer);
             break;
@@ -257,6 +282,14 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
 
     if (!dont_verify_domain && server) {}
 
+    if (error_no != SSL_ERROR_NONE && !SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) ) {
+        Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(error_no, peer_cert);
+        if(!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail,  errDetail)) {
+            debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer);
+            delete errDetail;
+        }
+    }
+
     return ok;
 }
 
@@ -526,55 +559,6 @@ ssl_parse_flags(const char *flags)
     return fl;
 }
 
-struct SslErrorMapEntry {
-    const char *name;
-    ssl_error_t value;
-};
-
-static SslErrorMapEntry TheSslErrorMap[] = {
-    { "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_CERT_NOT_YET_VALID", X509_V_ERR_CERT_NOT_YET_VALID },
-    { "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD", X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD },
-    { "X509_V_ERR_CERT_HAS_EXPIRED", X509_V_ERR_CERT_HAS_EXPIRED },
-    { "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD", X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD },
-    { "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY", X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY },
-    { "SSL_ERROR_NONE", SSL_ERROR_NONE },
-    { NULL, SSL_ERROR_NONE }
-};
-
-ssl_error_t
-sslParseErrorString(const char *name)
-{
-    assert(name);
-
-    for (int i = 0; TheSslErrorMap[i].name; ++i) {
-        if (strcmp(name, TheSslErrorMap[i].name) == 0)
-            return TheSslErrorMap[i].value;
-    }
-
-    if (xisdigit(*name)) {
-        const long int value = strtol(name, NULL, 0);
-        if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX)
-            return value;
-        fatalf("Too small or too bug SSL error code '%s'", name);
-    }
-
-    fatalf("Unknown SSL error name '%s'", name);
-    return SSL_ERROR_SSL; // not reached
-}
-
-const char *
-sslFindErrorString(ssl_error_t value)
-{
-    for (int i = 0; TheSslErrorMap[i].name; ++i) {
-        if (TheSslErrorMap[i].value == value)
-            return TheSslErrorMap[i].name;
-    }
-
-    return NULL;
-}
-
 // "dup" function for SSL_get_ex_new_index("cert_err_check")
 static int
 ssl_dupAclChecklist(CRYPTO_EX_DATA *, CRYPTO_EX_DATA *, void *,
@@ -594,6 +578,15 @@ ssl_freeAclChecklist(void *, void *ptr, CRYPTO_EX_DATA *,
     delete static_cast<ACLChecklist *>(ptr); // may be NULL
 }
 
+// "free" function for SSL_get_ex_new_index("ssl_error_detail")
+static void
+ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *,
+                     int, long, void *)
+{
+    Ssl::ErrorDetail  *errDetail = static_cast <Ssl::ErrorDetail *>(ptr);
+    delete errDetail;
+}
+
 /// \ingroup ServerProtocolSSLInternal
 static void
 ssl_initialize(void)
@@ -632,6 +625,7 @@ ssl_initialize(void)
     ssl_ex_index_server = SSL_get_ex_new_index(0, (void *) "server", NULL, NULL, NULL);
     ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL);
     ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist);
+    ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail);
 }
 
 /// \ingroup ServerProtocolSSLInternal
index 84bdaa661fb4950b0a0a9dda0a0dabd7b7324a71..2d106ed3db6a69320a9d9d1496e6f457abca6bc3 100644 (file)
@@ -89,10 +89,6 @@ const char *sslGetUserCertificatePEM(SSL *ssl);
 /// \ingroup ServerProtocolSSLAPI
 const char *sslGetUserCertificateChainPEM(SSL *ssl);
 
-typedef int ssl_error_t;
-ssl_error_t sslParseErrorString(const char *name);
-const char *sslFindErrorString(ssl_error_t value);
-
 namespace Ssl
 {
 /**
@@ -115,13 +111,28 @@ bool verifySslCertificateDate(SSL_CTX * sslContext);
  */
 SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data);
 
-} //namespace Ssl
+/**
+   \ingroup ServerProtocolSSLAPI
+   * Iterates over the X509 common and alternate names and to see if  matches with given data
+   * using the check_func.  
+   \param peer_cert  The X509 cert to check
+   \param check_data The data with which the X509 CNs compared
+   \param check_func The function used to match X509 CNs. The CN data passed as ASN1_STRING data
+   \return   1 if any of the certificate CN matches, 0 if none matches.
+ */
+int matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data,  ASN1_STRING *cn_data));
 
-// Custom SSL errors; assumes all official errors are positive
-#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_MAX INT_MAX
+/**
+   \ingroup ServerProtocolSSLAPI
+   * Convert a given ASN1_TIME to a string form.
+   \param tm the time in ASN1_TIME form
+   \param buf the buffer to write the output
+   \param len write at most len bytes
+   \return The number of bytes written
+ */
+int asn1timeToString(ASN1_TIME *tm, char *buf, int len);
+
+} //namespace Ssl
 
 #ifdef _SQUID_MSWIN_