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 #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 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 #endif #if HAVE_OPENSSL_X509V3_H #include #endif #if HAVE_OPENSSL_ERR_H #include #endif #if HAVE_OPENSSL_ENGINE_H #include #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);