#if USE_OPENSSL
#include "acl/Certificate.h"
#include "acl/CertificateData.h"
+#include "acl/ServerName.h"
#include "acl/SslError.h"
#include "acl/SslErrorData.h"
#endif
ACL::Prototype ACLAtStep::RegistryProtoype(&ACLAtStep::RegistryEntry_, "at_step");
ACLStrategised<Ssl::BumpStep> ACLAtStep::RegistryEntry_(new ACLAtStepData, ACLAtStepStrategy::Instance(), "at_step");
+
+ACL::Prototype ACLServerName::LiteralRegistryProtoype(&ACLServerName::LiteralRegistryEntry_, "ssl::server_name");
+ACLStrategised<char const *> ACLServerName::LiteralRegistryEntry_(new ACLServerNameData, ACLServerNameStrategy::Instance(), "ssl::server_name");
+ACL::Prototype ACLServerName::RegexRegistryProtoype(&ACLServerName::RegexRegistryEntry_, "ssl::server_name_regex");
+ACLFlag ServerNameRegexFlags[] = {ACL_F_REGEX_CASE, ACL_F_END};
+ACLStrategised<char const *> ACLServerName::RegexRegistryEntry_(new ACLRegexData, ACLServerNameStrategy::Instance(), "ssl::server_name_regex", ServerNameRegexFlags);
#endif
#if USE_SQUID_EUI
char *urlMakeAbsolute(const HttpRequest *, const char *);
char *urlRInternal(const char *host, unsigned short port, const char *dir, const char *name);
char *urlInternal(const char *dir, const char *name);
-int matchDomainName(const char *host, const char *domain);
+
+/**
+ * matchDomainName() compares a hostname (usually extracted from traffic)
+ * with a domainname (usually from an ACL) according to the following rules:
+ *
+ * HOST | DOMAIN | MATCH?
+ * -------------|-------------|------
+ * foo.com | foo.com | YES
+ * .foo.com | foo.com | YES
+ * x.foo.com | foo.com | NO
+ * foo.com | .foo.com | YES
+ * .foo.com | .foo.com | YES
+ * x.foo.com | .foo.com | YES
+ *
+ * We strip leading dots on hosts (but not domains!) so that
+ * ".foo.com" is always the same as "foo.com".
+ *
+ * if honorWildcards is true then the matchDomainName() also accepts
+ * optional wildcards on hostname:
+ *
+ * HOST | DOMAIN | MATCH?
+ * -------------|--------------|-------
+ * *.foo.com | x.foo.com | YES
+ * *.foo.com | .x.foo.com | YES
+ * *.foo.com | .foo.com | YES
+ * *.foo.com | foo.com | NO
+ *
+ * \retval 0 means the host matches the domain
+ * \retval 1 means the host is greater than the domain
+ * \retval -1 means the host is less than the domain
+ */
+int matchDomainName(const char *host, const char *domain, bool honorWildcards = false);
int urlCheckRequest(const HttpRequest *);
int urlDefaultPort(AnyP::ProtocolType p);
char *urlHostname(const char *url);
public:
virtual ~ACLDomainData();
- bool match(char const *);
+ virtual bool match(char const *);
virtual SBufList dump() const;
void parse();
bool empty() const;
RequestHeaderStrategy.h \
RequestMimeType.cc \
RequestMimeType.h \
+ ServerName.cc \
+ ServerName.h \
SourceAsn.h \
SourceDomain.cc \
SourceDomain.h \
--- /dev/null
+/*
+ * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+/* DEBUG: section 28 Access Control */
+
+#include "squid.h"
+#include "acl/Checklist.h"
+#include "acl/ServerName.h"
+#include "acl/DomainData.h"
+#include "acl/RegexData.h"
+#include "client_side.h"
+#include "fde.h"
+#include "HttpRequest.h"
+#include "ipcache.h"
+#include "SquidString.h"
+#include "ssl/bio.h"
+#include "ssl/ServerBump.h"
+#include "ssl/support.h"
+#include "URL.h"
+
+// Compare function for tree search algorithms
+static int
+aclHostDomainCompare( char *const &a, char * const &b)
+{
+ const char *h = static_cast<const char *>(a);
+ const char *d = static_cast<const char *>(b);
+ debugs(28, 7, "Match:" << h << " <> " << d);
+ return matchDomainName(h, d, true);
+}
+
+bool
+ACLServerNameData::match(const char *host)
+{
+ if (host == NULL)
+ return 0;
+
+ debugs(28, 3, "checking '" << host << "'");
+
+ char *h = const_cast<char *>(host);
+ char const * const * result = domains->find(h, aclHostDomainCompare);
+
+ debugs(28, 3, "'" << host << "' " << (result ? "found" : "NOT found"));
+
+ return (result != NULL);
+
+}
+
+ACLData<char const *> *
+ACLServerNameData::clone() const
+{
+ /* Splay trees don't clone yet. */
+ assert (!domains);
+ return new ACLServerNameData;
+}
+
+/// A helper function to be used with Ssl::matchX509CommonNames().
+/// \retval 0 when the name (cn or an alternate name) matches acl data
+/// \retval 1 when the name does not match
+template<class MatchType>
+int
+check_cert_domain( void *check_data, ASN1_STRING *cn_data)
+{
+ char cn[1024];
+ ACLData<MatchType> * data = (ACLData<MatchType> *)check_data;
+
+ if (cn_data->length > (int)sizeof(cn) - 1)
+ return 1; // ignore data that does not fit our buffer
+
+ memcpy(cn, cn_data->data, cn_data->length);
+ cn[cn_data->length] = '\0';
+ debugs(28, 4, "Verifying certificate name/subjectAltName " << cn);
+ if (data->match(cn))
+ return 0;
+ return 1;
+}
+
+int
+ACLServerNameStrategy::match (ACLData<MatchType> * &data, ACLFilledChecklist *checklist, ACLFlags &flags)
+{
+ assert(checklist != NULL && checklist->request != NULL);
+
+ if (checklist->conn() && checklist->conn()->serverBump()) {
+ if (X509 *peer_cert = checklist->conn()->serverBump()->serverCert.get()) {
+ if (Ssl::matchX509CommonNames(peer_cert, (void *)data, check_cert_domain<MatchType>))
+ return 1;
+ }
+ }
+
+ const char *serverName = NULL;
+ if (checklist->conn() && !checklist->conn()->sslCommonName().isEmpty()) {
+ SBuf scn = checklist->conn()->sslCommonName();
+ serverName = scn.c_str();
+ }
+
+ if (serverName == NULL)
+ serverName = checklist->request->GetHost();
+
+ if (serverName && data->match(serverName)) {
+ return 1;
+ }
+
+ return data->match("none");
+}
+
+ACLServerNameStrategy *
+ACLServerNameStrategy::Instance()
+{
+ return &Instance_;
+}
+
+ACLServerNameStrategy ACLServerNameStrategy::Instance_;
+
--- /dev/null
+/*
+ * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID_ACLSERVERNAME_H
+#define SQUID_ACLSERVERNAME_H
+
+#include "acl/Acl.h"
+#include "acl/Checklist.h"
+#include "acl/Data.h"
+#include "acl/Strategised.h"
+#include "acl/DomainData.h"
+
+class ACLServerNameData : public ACLDomainData {
+ MEMPROXY_CLASS(ACLServerNameData);
+public:
+ ACLServerNameData() : ACLDomainData(){}
+ virtual bool match(const char *);
+ virtual ACLData<char const *> *clone() const;
+};
+
+class ACLServerNameStrategy : public ACLStrategy<char const *>
+{
+
+public:
+ virtual int match (ACLData<MatchType> * &, ACLFilledChecklist *, ACLFlags &);
+ static ACLServerNameStrategy *Instance();
+ virtual bool requiresRequest() const {return true;}
+
+ /**
+ * Not implemented to prevent copies of the instance.
+ \par
+ * Not private to prevent brain dead g+++ warnings about
+ * private constructors with no friends
+ */
+ ACLServerNameStrategy(ACLServerNameStrategy const &);
+
+private:
+ static ACLServerNameStrategy Instance_;
+ ACLServerNameStrategy() {}
+
+ ACLServerNameStrategy&operator=(ACLServerNameStrategy const &);
+};
+
+class ACLServerName
+{
+
+private:
+ static ACL::Prototype LiteralRegistryProtoype;
+ static ACLStrategised<char const *> LiteralRegistryEntry_;
+ static ACL::Prototype RegexRegistryProtoype;
+ static ACLStrategised<char const *> RegexRegistryEntry_;
+};
+
+#endif /* SQUID_ACLSERVERNAME_H */
+
# SslBump1: After getting TCP-level and HTTP CONNECT info.
# SslBump2: After getting SSL Client Hello info.
# SslBump3: After getting SSL Server Hello info.
+
+ acl aclname ssl::server_name .foo.com ...
+ # matches server name obtained from various sources [fast]
+ #
+ # The server name is obtained during Ssl-Bump steps from such sources
+ # as CONNECT request URI, client SNI, and SSL server certificate CN.
+ # During each Ssl-Bump step, Squid may improve its understanding of a
+ # "true server name". Unlike dstdomain, this ACL does not perform
+ # DNS lookups.
+
+ acl aclname ssl::server_name_regex [-i] \.foo\.com ...
+ # regex matches server name obtained from various sources [fast]
ENDIF
acl aclname any-of acl1 acl2 ...
# match any one of the acls [fast or slow]
" has no certificate.");
}
+#if defined(TLSEXT_NAMETYPE_host_name)
+ if (!conn->serverBump()) {
+ // when in bumpClientFirst mode, get the server name from SNI
+ if (const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name))
+ conn->resetSslCommonName(server);
+ }
+#endif
+
conn->readSomeData();
}
void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties)
{
- certProperties.commonName = sslCommonName.size() > 0 ? sslCommonName.termedBuf() : sslConnectHostOrIp.termedBuf();
+ certProperties.commonName = sslCommonName_.isEmpty() ? sslConnectHostOrIp.termedBuf() : sslCommonName_.c_str();
// fake certificate adaptation requires bump-server-first mode
if (!sslServerBump) {
assert(!switchedToHttps_);
sslConnectHostOrIp = request->GetHost();
- sslCommonName = request->GetHost();
+ resetSslCommonName(request->GetHost());
// We are going to read new request
flags.readMore = true;
if (bio->gotHello()) {
if (conn->serverBump()) {
Ssl::Bio::sslFeatures const &features = bio->getFeatures();
- if (!features.serverName.isEmpty())
+ if (!features.serverName.isEmpty()) {
conn->serverBump()->clientSni = features.serverName;
+ conn->resetSslCommonName(features.serverName.c_str());
+ }
}
debugs(83, 5, "I got hello. Start forwarding the request!!! ");
Must(sslServerBump != NULL);
if (Comm::IsConnOpen(serverConnection)) {
- SSL *ssl = fd_table[serverConnection->fd].ssl;
- assert(ssl);
- Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
- assert(serverCert.get() != NULL);
- sslCommonName = Ssl::CommonHostName(serverCert.get());
- debugs(33, 5, HERE << "HTTPS server CN: " << sslCommonName <<
- " bumped: " << *serverConnection);
-
pinConnection(serverConnection, NULL, NULL, false);
debugs(33, 5, HERE << "bumped HTTPS server: " << sslConnectHostOrIp);
} else {
debugs(33, 5, HERE << "Error while bumping: " << sslConnectHostOrIp);
- Ip::Address intendedDest;
- intendedDest = sslConnectHostOrIp.termedBuf();
- const bool isConnectRequest = !port->flags.isIntercepted();
-
- // Squid serves its own error page and closes, so we want
- // a CN that causes no additional browser errors. Possible
- // only when bumping CONNECT with a user-typed address.
- if (intendedDest.isAnyAddr() || isConnectRequest)
- sslCommonName = sslConnectHostOrIp;
- else if (sslServerBump->serverCert.get())
- sslCommonName = Ssl::CommonHostName(sslServerBump->serverCert.get());
// copy error detail from bump-server-first request to CONNECT request
if (currentobject != NULL && currentobject->http != NULL && currentobject->http->request)
else
assert(sslServerBump == srvBump);
}
+ const SBuf &sslCommonName() const {return sslCommonName_;}
+ void resetSslCommonName(const char *name) {sslCommonName_ = name;}
/// Fill the certAdaptParams with the required data for certificate adaptation
/// and create the key for storing/retrieve the certificate to/from the cache
void buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties);
bool switchedToHttps_;
/// The SSL server host name appears in CONNECT request or the server ip address for the intercepted requests
String sslConnectHostOrIp; ///< The SSL server host name as passed in the CONNECT request
- String sslCommonName; ///< CN name for SSL certificate generation
+ SBuf sslCommonName_; ///< CN name for SSL certificate generation
String sslBumpCertKey; ///< Key to use to store/retrieve generated certificate
/// HTTPS server cert. fetching state for bump-ssl-server-first
callback(aCallback),
negotiationTimeout(timeout),
startTime(squid_curtime),
- splice(false)
+ splice(false),
+ serverCertificateHandled(false)
{
// if this throws, the caller's cb dialer is not our CbDialer
Must(dynamic_cast<CbDialer*>(callback->getDialer()));
callBack();
}
+void
+Ssl::PeerConnector::handleServerCertificate()
+{
+ if (serverCertificateHandled)
+ return;
+
+ if (ConnStateData *csd = request->clientConnectionManager.valid()) {
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ Ssl::X509_Pointer serverCert(SSL_get_peer_certificate(ssl));
+ if (!serverCert.get())
+ return;
+
+ serverCertificateHandled = true;
+
+ csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
+ debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
+ " bumped: " << *serverConnection());
+
+ // remember the server certificate for later use
+ if (Ssl::ServerBump *serverBump = csd->serverBump()) {
+ serverBump->serverCert.reset(serverCert.release());
+ }
+ }
+}
+
bool
Ssl::PeerConnector::sslFinalized()
{
const int fd = serverConnection()->fd;
SSL *ssl = fd_table[fd].ssl;
- if (request->clientConnectionManager.valid()) {
- // remember the server certificate from the ErrorDetail object
- if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
- serverBump->serverCert.reset(SSL_get_peer_certificate(ssl));
+ handleServerCertificate();
+ if (ConnStateData *csd = request->clientConnectionManager.valid()) {
+ if (Ssl::ServerBump *serverBump = csd->serverBump()) {
// remember validation errors, if any
if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
serverBump->sslErrors = cbdataReference(errs);
void
Ssl::PeerConnector::checkForPeekAndSplice()
{
- SSL *ssl = fd_table[serverConn->fd].ssl;
// Mark Step3 of bumping
if (request->clientConnectionManager.valid()) {
if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
serverBump->step = Ssl::bumpStep3;
- if (!serverBump->serverCert.get())
- serverBump->serverCert.reset(SSL_get_peer_certificate(ssl));
}
}
+ handleServerCertificate();
+
ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(
::Config.accessList.ssl_bump,
request.getRaw(), NULL);
/// Check SSL errors returned from cert validator against sslproxy_cert_error access list
Ssl::CertErrors *sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &, Ssl::ErrorDetail *&);
+ /// Updates associated client connection manager members
+ /// if the server certificate was received from the server.
+ void handleServerCertificate();
+
/// Callback function called when squid receive message from cert validator helper
static void sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &);
time_t negotiationTimeout; ///< the ssl connection timeout to use
time_t startTime; ///< when the peer connector negotiation started
bool splice; ///< Whether we are going to splice or not
+ bool serverCertificateHandled; ///< whether handleServerCertificate() succeeded
};
} // namespace Ssl
return (urlbuf);
}
-/*
- * matchDomainName() compares a hostname with a domainname according
- * to the following rules:
- *
- * HOST DOMAIN MATCH?
- * ------------- ------------- ------
- * foo.com foo.com YES
- * .foo.com foo.com YES
- * x.foo.com foo.com NO
- * foo.com .foo.com YES
- * .foo.com .foo.com YES
- * x.foo.com .foo.com YES
- *
- * We strip leading dots on hosts (but not domains!) so that
- * ".foo.com" is is always the same as "foo.com".
- *
- * Return values:
- * 0 means the host matches the domain
- * 1 means the host is greater than the domain
- * -1 means the host is less than the domain
- */
-
int
-matchDomainName(const char *h, const char *d)
+matchDomainName(const char *h, const char *d, bool honorWildcards)
{
int dl;
int hl;
/*
* We found different characters in the same position (from the end).
*/
+
+ // If the h has a form of "*.foo.com" and d has a form of "x.foo.com"
+ // then the h[hl] points to '*', h[hl+1] to '.' and d[dl] to 'x'
+ // The following checks are safe, the "h[hl + 1]" in the worst case is '\0'.
+ if (honorWildcards && h[hl] == '*' && h[hl + 1] == '.')
+ return 0;
+
/*
* If one of those character is '.' then its special. In order
* for splay tree sorting to work properly, "x-foo.com" must