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);