#include "neighbors.h"
#include "pconn.h"
#include "PeerSelectState.h"
- #include "protos.h"
+ #include "SquidConfig.h"
#include "SquidTime.h"
#include "Store.h"
+ #include "StoreClient.h"
+ #include "urn.h"
#include "whois.h"
#if USE_SSL
+#include "ssl/cert_validate_message.h"
+#include "ssl/Config.h"
+#include "ssl/helper.h"
#include "ssl/support.h"
#include "ssl/ErrorDetail.h"
#include "ssl/ServerBump.h"
serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
}
- Ssl::CertValidationMsg requestMsg;
+#if 1 // USE_SSL_CERT_VALIDATOR
+ if (Ssl::TheConfig.ssl_crt_validator) {
+ Ssl::CertValidationRequest validationRequest;
+ // WARNING: The STACK_OF(*) OpenSSL objects does not support locking.
+ // If we need to support locking we need to sk_X509_dup the STACK_OF(X509)
+ // list and lock all of the X509 members of the list.
+ // Currently we do not use any locking for any of the members of the
+ // Ssl::CertValidationRequest class. If the ssl object gone, the value returned
+ // from SSL_get_peer_cert_chain may not exist any more. In this code the
+ // Ssl::CertValidationRequest object used only to pass data to
+ // Ssl::CertValidationHelper::submit method.
+ validationRequest.peerCerts = SSL_get_peer_cert_chain(ssl);
+ validationRequest.domainName = request->GetHost();
+ if (Ssl::Errors *errs = static_cast<Ssl::Errors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+ // validationRequest disappears on return so no need to cbdataReference
+ validationRequest.errors = errs;
+ else
+ validationRequest.errors = NULL;
+ try {
+ debugs(83, 5, HERE << "Sending SSL certificate for validation to ssl_crtvd.");
++ Ssl::CertValidationMsg requestMsg(Ssl::CrtdMessage::REQUEST);
+ requestMsg.setCode(Ssl::CertValidationMsg::code_cert_validate);
+ requestMsg.composeRequest(validationRequest);
+ debugs(83, 5, HERE << "SSL crtvd request: " << requestMsg.compose().c_str());
+ Ssl::CertValidationHelper::GetInstance()->sslSubmit(requestMsg, sslCrtvdHandleReplyWrapper, this);
+ return;
+ } catch (const std::exception &e) {
+ debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtd " <<
+ "request for " << validationRequest.domainName <<
+ " certificate: " << e.what() << "; will now block to " <<
+ "validate that certificate.");
+ // fall through to do blocking in-process generation.
+ ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
+ fail(anErr);
+ if (serverConnection()->getPeer()) {
+ peerConnectFailed(serverConnection()->getPeer());
+ }
+ serverConn->close();
+ self = NULL;
+ return;
+ }
+ }
+#endif // USE_SSL_CERT_VALIDATOR
+
dispatch();
}
- FwdState::sslCrtvdHandleReplyWrapper(void *data, char *reply)
+#if 1 // USE_SSL_CERT_VALIDATOR
+void
- FwdState::sslCrtvdHandleReply(const char *reply)
++FwdState::sslCrtvdHandleReplyWrapper(void *data, const HelperReply &reply)
+{
+ FwdState * fwd = (FwdState *)(data);
+ fwd->sslCrtvdHandleReply(reply);
+}
+
+void
- if (!reply) {
++FwdState::sslCrtvdHandleReply(const HelperReply &reply)
+{
+ Ssl::Errors *errs = NULL;
+ Ssl::ErrorDetail *errDetails = NULL;
+ bool validatorFailed = false;
+ if (!Comm::IsConnOpen(serverConnection())) {
+ return;
+ }
+ SSL *ssl = fd_table[serverConnection()->fd].ssl;
+
- Ssl::CertValidationMsg replyMsg;
++ if (!reply.other().hasContent()) {
+ debugs(83, 1, HERE << "\"ssl_crtvd\" helper return <NULL> reply");
+ validatorFailed = true;
+ } else {
- if (replyMsg.parse(reply, strlen(reply)) != Ssl::CrtdMessage::OK ||
++ Ssl::CertValidationMsg replyMsg(Ssl::CrtdMessage::REPLY);
+ Ssl::CertValidationResponse validationResponse;
+ std::string error;
+ STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(ssl);
- if (replyMsg.getCode() == "OK") {
++ if (replyMsg.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK ||
+ !replyMsg.parseResponse(validationResponse, peerCerts, error) ) {
+ debugs(83, 5, HERE << "Reply from ssl_crtvd for " << request->GetHost() << " is incorrect");
+ validatorFailed = true;
+ } else {
- } else if (replyMsg.getCode() == "ERR") {
++ if (reply.result == HelperReply::Okay) {
+ debugs(83, 5, HERE << "Certificate for " << request->GetHost() << " was successfully validated from ssl_crtvd");
++ } else if (reply.result == HelperReply::Error) {
+ debugs(83, 5, HERE << "Certificate for " << request->GetHost() << " found buggy by ssl_crtvd");
+ errs = sslCrtvdCheckForErrors(validationResponse, errDetails);
+ } else {
+ debugs(83, 5, HERE << "Certificate for " << request->GetHost() << " cannot be validated. ssl_crtvd response: " << replyMsg.getBody());
+ validatorFailed = true;
+ }
+
+ if (!errDetails && !validatorFailed) {
+ dispatch();
+ return;
+ }
+ }
+ }
+
+ ErrorState *anErr = NULL;
+ if (validatorFailed) {
+ anErr = new ErrorState(ERR_GATEWAY_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request);
+ } else {
+
+ // Check the list error with
+ if (errDetails && request->clientConnectionManager.valid()) {
+ // remember the server certificate from the ErrorDetail object
+ if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
+ // remember validation errors, if any
+ if (errs) {
+ if (serverBump->sslErrors)
+ cbdataReferenceDone(serverBump->sslErrors);
+ serverBump->sslErrors = cbdataReference(errs);
+ }
+ }
+ }
+
+ anErr = makeConnectingError(ERR_SECURE_CONNECT_FAIL);
+ anErr->detail = errDetails;
+ /*anErr->xerrno= Should preserved*/
+ }
+
+ fail(anErr);
+ if (serverConnection()->getPeer()) {
+ peerConnectFailed(serverConnection()->getPeer());
+ }
+ serverConn->close();
+ self = NULL;
+ return;
+}
+
+/// Checks errors in the cert. validator response against sslproxy_cert_error.
+/// The first honored error, if any, is returned via errDetails parameter.
+/// The method returns all seen errors except SSL_ERROR_NONE as Ssl::Errors.
+Ssl::Errors *
+FwdState::sslCrtvdCheckForErrors(Ssl::CertValidationResponse &resp, Ssl::ErrorDetail *& errDetails)
+{
+ Ssl::Errors *errs = NULL;
+
+ ACLFilledChecklist *check = NULL;
+ if (acl_access *acl = Config.ssl_client.cert_error)
+ check = new ACLFilledChecklist(acl, request, dash_str);
+
+ SSL *ssl = fd_table[serverConnection()->fd].ssl;
+ typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
+ for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
+ debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
+
+ if (i->error_no == SSL_ERROR_NONE)
+ continue; //ignore????
+
+ if (!errDetails) {
+ bool allowed = false;
+ if (check) {
+ check->sslErrors = new Ssl::Errors(i->error_no);
+ if (check->fastCheck() == ACCESS_ALLOWED)
+ allowed = true;
+ }
+ // else the Config.ssl_client.cert_error access list is not defined
+ // and the first error will cause the error page
+
+ if (allowed) {
+ debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
+ } else {
+ debugs(83, 5, "confirming SSL error " << i->error_no);
+ X509 *brokenCert = i->cert;
+ X509 *peerCert = SSL_get_peer_certificate(ssl);
+ const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
+ errDetails = new Ssl::ErrorDetail(i->error_no, peerCert, brokenCert, aReason);
+ X509_free(peerCert);
+ }
+ delete check->sslErrors;
+ check->sslErrors = NULL;
+ }
+
+ if (!errs)
+ errs = new Ssl::Errors(i->error_no);
+ else
+ errs->push_back_unique(i->error_no);
+ }
+ if (check)
+ delete check;
+
+ return errs;
+}
+
+#endif // USE_SSL_CERT_VALIDATOR
+
void
FwdState::initiateSSL()
{
Ssl::setClientSNI(ssl, hostname);
}
- // Create the ACL check list now, while we have access to more info.
- // The list is used in ssl_verify_cb() and is freed in ssl_free().
- if (acl_access *acl = Config.ssl_client.cert_error) {
- ACLFilledChecklist *check = new ACLFilledChecklist(acl, request, dash_str);
- SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
+#if 1 // USE_SSL_CERT_VALIDATOR
+ // If CertValidation Helper used do not lookup checklist for errors,
+ // but keep a list of errors to send it to CertValidator
+ if (!Ssl::TheConfig.ssl_crt_validator) {
+#endif
+ // Create the ACL check list now, while we have access to more info.
+ // The list is used in ssl_verify_cb() and is freed in ssl_free().
+ if (acl_access *acl = Config.ssl_client.cert_error) {
+ ACLFilledChecklist *check = new ACLFilledChecklist(acl, request, dash_str);
- if (Comm::IsConnOpen(clientConn))
- check->fd(clientConn->fd);
+ SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
+ }
+#if 1 // USE_SSL_CERT_VALIDATOR
}
+#endif
// store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
X509 *peeked_cert;
--- /dev/null
- CertValidationMsg(): CrtdMessage() {}
+/*
+ * $Id$
+ */
+
+#ifndef SQUID_SSL_CERT_VALIDATE_MESSAGE_H
+#define SQUID_SSL_CERT_VALIDATE_MESSAGE_H
+
+#include "ssl/support.h"
+#include "ssl/crtd_message.h"
+#include <vector>
+
+namespace Ssl
+{
+
+/**
+ * This class is used to hold the required informations to build
+ * a request message for the certificate validator helper
+ */
+class CertValidationRequest
+{
+public:
+ STACK_OF(X509) *peerCerts; ///< The list of sent by SSL server
+ Errors *errors; ///< The list of errors detected
+ std::string domainName; ///< The server name
+ CertValidationRequest() : peerCerts(NULL), errors(NULL) {}
+};
+
+/**
+ * This class is used to store informations found in certificate validation
+ * response messages read from certificate validator helper
+ */
+class CertValidationResponse
+{
+public:
+ /**
+ * This class used to hold error informations returned from
+ * cert validator helper.
+ */
+ class RecvdError
+ {
+ public:
+ RecvdError(): id(0), error_no(SSL_ERROR_NONE), cert(NULL) {}
+ RecvdError(const RecvdError &);
+ ~RecvdError();
+ RecvdError & operator = (const RecvdError &);
+ void setCert(X509 *); ///< Sets cert to the given certificate
+ int id; ///< The id of the error
+ ssl_error_t error_no; ///< The OpenSSL error code
+ std::string error_reason; ///< A string describing the error
+ X509 *cert; ///< The broken certificate
+ };
+
+ typedef std::vector<RecvdError> RecvdErrors;
+
+ /// Search in errors list for the error item with id=errorId.
+ /// If none found a new RecvdError item added with the given id;
+ RecvdError &getError(int errorId);
+ RecvdErrors errors; ///< The list of parsed errors
+};
+
+/**
+ * This class is responsible for composing or parsing messages destined to
+ * or comming from a cert validator helper.
+ * The messages format is:
+ * <response/request code> <whitespace> <body length> <whitespace> <key=value> ...\1
+ */
+class CertValidationMsg: public CrtdMessage
+{
+private:
+ /**
+ * This class used to hold the certId/cert pairs found
+ * in cert validation messages.
+ */
+ class CertItem
+ {
+ public:
+ std::string name; ///< The certificate Id to use
+ X509 *cert; ///< A pointer to certificate
+ CertItem(): cert(NULL) {}
+ CertItem(const CertItem &);
+ CertItem & operator = (const CertItem &);
+ ~CertItem();
+ void setCert(X509 *); ///< Sets cert to the given certificate
+ };
+
+public:
++ CertValidationMsg(MessageKind kind): CrtdMessage(kind) {}
+
+ /// Build a request message for the cert validation helper
+ /// using informations provided by vcert object
+ void composeRequest(CertValidationRequest const &vcert);
+
+ /// Parse a response message and fill the resp object with parsed informations
+ bool parseResponse(CertValidationResponse &resp, STACK_OF(X509) *peerCerts, std::string &error);
+
+ /// Search a CertItems list for the certificate with ID "name"
+ X509 *getCertByName(std::vector<CertItem> const &, std::string const & name);
+
+ /// String code for "cert_validate" messages
+ static const std::string code_cert_validate;
+ /// Parameter name for passing intended domain name
+ static const std::string param_domain;
+ /// Parameter name for passing SSL errors
+ static const std::string param_error;
+ /// Parameter name for passing SSL certificates
+ static const std::string param_cert;
+ /// Parameter name for passing the major SSL error
+ static const std::string param_error_name;
+ /// Parameter name for passing the error reason
+ static const std::string param_error_reason;
+ /// Parameter name for passing the error cert ID
+ static const std::string param_error_cert;
+};
+
+}//namespace Ssl
+#endif // SQUID_SSL_CERT_VALIDATE_MESSAGE_H
msg += '\n';
helperSubmit(ssl_crtd, msg.c_str(), callback, data);
}
- callback(data, (char *)"error 45 Temporary network problem, please retry later");
+
+/*ssl_crtd_validator*/
+
+Ssl::CertValidationHelper * Ssl::CertValidationHelper::GetInstance()
+{
+ static Ssl::CertValidationHelper sslHelper;
+ if (!Ssl::TheConfig.ssl_crt_validator)
+ return NULL;
+ return &sslHelper;
+}
+
+Ssl::CertValidationHelper::CertValidationHelper() : ssl_crt_validator(NULL)
+{
+}
+
+Ssl::CertValidationHelper::~CertValidationHelper()
+{
+ Shutdown();
+}
+
+void Ssl::CertValidationHelper::Init()
+{
+ assert(ssl_crt_validator == NULL);
+
+ // we need to start ssl_crtd only if some port(s) need to bump SSL
+ bool found = false;
+ for (AnyP::PortCfg *s = ::Config.Sockaddr.http; !found && s; s = s->next)
+ found = s->sslBump;
+ for (AnyP::PortCfg *s = ::Config.Sockaddr.https; !found && s; s = s->next)
+ found = s->sslBump;
+ if (!found)
+ return;
+
+ ssl_crt_validator = new helper("ssl_crt_validator");
+ ssl_crt_validator->childs.updateLimits(Ssl::TheConfig.ssl_crt_validator_Children);
+ ssl_crt_validator->ipc_type = IPC_STREAM;
+ // The crtd messages may contain the eol ('\n') character. We are
+ // going to use the '\1' char as the end-of-message mark.
+ ssl_crt_validator->eom = '\1';
+ assert(ssl_crt_validator->cmdline == NULL);
+ {
+ char *tmp = xstrdup(Ssl::TheConfig.ssl_crt_validator);
+ char *tmp_begin = tmp;
+ char * token = NULL;
+ while ((token = strwordtok(NULL, &tmp))) {
+ wordlistAdd(&ssl_crt_validator->cmdline, token);
+ }
+ safe_free(tmp_begin);
+ }
+ helperOpenServers(ssl_crt_validator);
+}
+
+void Ssl::CertValidationHelper::Shutdown()
+{
+ if (!ssl_crt_validator)
+ return;
+ helperShutdown(ssl_crt_validator);
+ wordlistDestroy(&ssl_crt_validator->cmdline);
+ delete ssl_crt_validator;
+ ssl_crt_validator = NULL;
+}
+
+void Ssl::CertValidationHelper::sslSubmit(CrtdMessage const & message, HLPCB * callback, void * data)
+{
+ static time_t first_warn = 0;
+ assert(ssl_crt_validator);
+
+ if (ssl_crt_validator->stats.queue_size >= (int)(ssl_crt_validator->childs.n_running * 2)) {
+ if (first_warn == 0)
+ first_warn = squid_curtime;
+ if (squid_curtime - first_warn > 3 * 60)
+ fatal("SSL servers not responding for 3 minutes");
+ debugs(83, 1, HERE << "Queue overload, rejecting");
++ const char *errMsg = "BH error 45 Temporary network problem, please retry later";
++ HelperReply failReply(errMsg,strlen(errMsg));
++ callback(data, failReply);
+ return;
+ }
+
+ first_warn = 0;
+ std::string msg = message.compose();
+ msg += '\n';
+ helperSubmit(ssl_crt_validator, msg.c_str(), callback, data);
+}