From: Christos Tsantilas Date: Mon, 13 Dec 2010 23:02:52 +0000 (+0200) Subject: Report ERR_SECURE_CONNECT_FAIL details to the user via a new error detail API. X-Git-Tag: take00~42 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4d16918eadf2b696b160599b3a013310da420ea7;p=thirdparty%2Fsquid.git Report ERR_SECURE_CONNECT_FAIL details to the user via a new error detail API. 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 --- diff --git a/src/acl/SslErrorData.cc b/src/acl/SslErrorData.cc index 3de564c1c8..93381a6177 100644 --- a/src/acl/SslErrorData.cc +++ b/src/acl/SslErrorData.cc @@ -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 instantiation for other ACLs. -// template cbdata_type CbDataList::CBDATA_CbDataList; +// template cbdata_type CbDataList::CBDATA_CbDataList; /** \endcond */ wordlist * ACLSslErrorData::dump() { wordlist *W = NULL; - CbDataList *data = values; + CbDataList *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 **Tail; + CbDataList **Tail; char *t = NULL; for (Tail = &values; *Tail; Tail = &((*Tail)->next)); while ((t = strtokFile())) { - CbDataList *q = new CbDataList(sslParseErrorString(t)); + CbDataList *q = new CbDataList(Ssl::parseErrorString(t)); *(Tail) = q; Tail = &q->next; } @@ -67,7 +67,7 @@ ACLSslErrorData::empty() const return values == NULL; } -ACLData * +ACLData * ACLSslErrorData::clone() const { /* Splay trees don't clone yet. */ diff --git a/src/acl/SslErrorData.h b/src/acl/SslErrorData.h index fc0afff549..d2974921a9 100644 --- a/src/acl/SslErrorData.h +++ b/src/acl/SslErrorData.h @@ -9,8 +9,9 @@ #include "acl/Data.h" #include "CbDataList.h" #include "ssl/support.h" +#include "ssl/ErrorDetail.h" -class ACLSslErrorData : public ACLData +class ACLSslErrorData : public ACLData { 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 *clone() const; + virtual ACLData *clone() const; - CbDataList *values; + CbDataList *values; }; MEMPROXY_CLASS_INLINE(ACLSslErrorData); diff --git a/src/errorpage.cc b/src/errorpage.cc index af4edeedc5..ccdbfccdb8 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -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 */ } diff --git a/src/errorpage.h b/src/errorpage.h index 74a30b6efc..e3ea32cfa3 100644 --- a/src/errorpage.h +++ b/src/errorpage.h @@ -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); }; diff --git a/src/forward.cc b/src/forward.cc index bfa6d4792f..f20bb209a7 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -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) { diff --git a/src/globals.h b/src/globals.h index 0ac3da8aeb..3fc2007e97 100644 --- a/src/globals.h +++ b/src/globals.h @@ -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 index 0000000000..ac0d5a5336 --- /dev/null +++ b/src/ssl/ErrorDetail.cc @@ -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 index 0000000000..7a2d847b0a --- /dev/null +++ b/src/ssl/ErrorDetail.h @@ -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 +#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 diff --git a/src/ssl/Makefile.am b/src/ssl/Makefile.am index 8aad6f4698..6eb6756a66 100644 --- a/src/ssl/Makefile.am +++ b/src/ssl/Makefile.am @@ -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 \ diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 0f51098606..0feeab361e 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -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(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 (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 diff --git a/src/ssl/support.h b/src/ssl/support.h index 84bdaa661f..2d106ed3db 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -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_