Hi all,
This patch adds server_name ACL matching server name(s) obtained from
various sources such as CONNECT request URI, client SNI, and SSL server
certificate CN.
During each SslBump step, Squid improves its understanding of a "true
server name", with a bias towards server-provided (and Squid-validated)
information.
The server-provided server names are retrieved from the server
certificate CN and Subject Alternate Names. The new server_name ACL
matches any of alternate names and CN. If the CN or an alternate name is
a wildcard, then the new ACL matches any domain that matches the domain
with the wildcard.
Other than supporting many sources of server name information (including
sources that may supply Squid with multiple server name variants and
wildcards), the new ACL is similar to dstdomain.
Also added a server_name_regex ACL.
Add server_name ACL matching server name(s) obtained from various sources
such as CONNECT request URI, client SNI, and SSL server certificate CN.
During each SslBump step, Squid improves its understanding of a "true server
name", with a bias towards server-provided (and Squid-validated) information.
The server-provided server names are retrieved from the server certificate CN
and Subject Alternate Names. The new server_name ACL matches any of alternate
names and CN. If the CN or an alternate name is a wildcard, then the new ACL
matches any domain that matches the domain with the wildcard.
Other than supporting many sources of server name information (including
sources that may supply Squid with multiple server name variants and
wildcards), the new ACL is similar to dstdomain.
Also added a server_name_regex ACL.
This is a Measurement Factory project.
=== modified file 'src/AclRegs.cc'
--- src/AclRegs.cc 2015-01-16 18:12:04 +0000
+++ src/AclRegs.cc 2015-02-13 11:39:50 +0000
@@ -57,40 +57,41 @@
#include "acl/Note.h"
#include "acl/NoteData.h"
#include "acl/PeerName.h"
#include "acl/Protocol.h"
#include "acl/ProtocolData.h"
#include "acl/Random.h"
#include "acl/Referer.h"
#include "acl/RegexData.h"
#include "acl/ReplyHeaderStrategy.h"
#include "acl/ReplyMimeType.h"
#include "acl/RequestHeaderStrategy.h"
#include "acl/RequestMimeType.h"
#include "acl/SourceAsn.h"
#include "acl/SourceDomain.h"
#include "acl/SourceIp.h"
#include "acl/SquidError.h"
#include "acl/SquidErrorData.h"
#if USE_OPENSSL
#include "acl/Certificate.h"
#include "acl/CertificateData.h"
+#include "acl/ServerName.h"
#include "acl/SslError.h"
#include "acl/SslErrorData.h"
#endif
#include "acl/Strategised.h"
#include "acl/Strategy.h"
#include "acl/StringData.h"
#if USE_OPENSSL
#include "acl/ServerCertificate.h"
#endif
#include "acl/Tag.h"
#include "acl/Time.h"
#include "acl/TimeData.h"
#include "acl/Url.h"
#include "acl/UrlLogin.h"
#include "acl/UrlPath.h"
#include "acl/UrlPort.h"
#include "acl/UserData.h"
#if USE_AUTH
#include "auth/AclMaxUserIp.h"
#include "auth/AclProxyAuth.h"
@@ -160,40 +161,46 @@
ACL::Prototype ACLUrlLogin::RegistryProtoype(&ACLUrlLogin::RegistryEntry_, "urllogin");
ACLStrategised<char const *> ACLUrlLogin::RegistryEntry_(new ACLRegexData, ACLUrlLoginStrategy::Instance(), "urllogin");
ACL::Prototype ACLUrlPath::LegacyRegistryProtoype(&ACLUrlPath::RegistryEntry_, "pattern");
ACL::Prototype ACLUrlPath::RegistryProtoype(&ACLUrlPath::RegistryEntry_, "urlpath_regex");
ACLStrategised<char const *> ACLUrlPath::RegistryEntry_(new ACLRegexData, ACLUrlPathStrategy::Instance(), "urlpath_regex");
ACL::Prototype ACLUrlPort::RegistryProtoype(&ACLUrlPort::RegistryEntry_, "port");
ACLStrategised<int> ACLUrlPort::RegistryEntry_(new ACLIntRange, ACLUrlPortStrategy::Instance(), "port");
#if USE_OPENSSL
ACL::Prototype ACLSslError::RegistryProtoype(&ACLSslError::RegistryEntry_, "ssl_error");
ACLStrategised<const Ssl::CertErrors *> ACLSslError::RegistryEntry_(new ACLSslErrorData, ACLSslErrorStrategy::Instance(), "ssl_error");
ACL::Prototype ACLCertificate::UserRegistryProtoype(&ACLCertificate::UserRegistryEntry_, "user_cert");
ACLStrategised<X509 *> ACLCertificate::UserRegistryEntry_(new ACLCertificateData (Ssl::GetX509UserAttribute, "*"), ACLCertificateStrategy::Instance(), "user_cert");
ACL::Prototype ACLCertificate::CARegistryProtoype(&ACLCertificate::CARegistryEntry_, "ca_cert");
ACLStrategised<X509 *> ACLCertificate::CARegistryEntry_(new ACLCertificateData (Ssl::GetX509CAAttribute, "*"), ACLCertificateStrategy::Instance(), "ca_cert");
ACL::Prototype ACLServerCertificate::X509FingerprintRegistryProtoype(&ACLServerCertificate::X509FingerprintRegistryEntry_, "server_cert_fingerprint");
ACLStrategised<X509 *> ACLServerCertificate::X509FingerprintRegistryEntry_(new ACLCertificateData(Ssl::GetX509Fingerprint, "-sha1", true), ACLServerCertificateStrategy::Instance(), "server_cert_fingerprint");
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_, "server_name");
+ACLStrategised<char const *> ACLServerName::LiteralRegistryEntry_(new ACLServerNameData, ACLServerNameStrategy::Instance(), "server_name");
+ACL::Prototype ACLServerName::RegexRegistryProtoype(&ACLServerName::RegexRegistryEntry_, "server_name_regex");
+ACLFlag ServerNameRegexFlags[] = {ACL_F_REGEX_CASE, ACL_F_END};
+ACLStrategised<char const *> ACLServerName::RegexRegistryEntry_(new ACLRegexData, ACLServerNameStrategy::Instance(), "server_name_regex", ServerNameRegexFlags);
#endif
#if USE_SQUID_EUI
ACL::Prototype ACLARP::RegistryProtoype(&ACLARP::RegistryEntry_, "arp");
ACLARP ACLARP::RegistryEntry_("arp");
ACL::Prototype ACLEui64::RegistryProtoype(&ACLEui64::RegistryEntry_, "eui64");
ACLEui64 ACLEui64::RegistryEntry_("eui64");
#endif
#if USE_IDENT
ACL::Prototype ACLIdent::UserRegistryProtoype(&ACLIdent::UserRegistryEntry_, "ident");
ACLIdent ACLIdent::UserRegistryEntry_(new ACLUserData, "ident");
ACL::Prototype ACLIdent::RegexRegistryProtoype(&ACLIdent::RegexRegistryEntry_, "ident_regex" );
ACLIdent ACLIdent::RegexRegistryEntry_(new ACLRegexData, "ident_regex");
#endif
#if USE_AUTH
ACL::Prototype ACLProxyAuth::UserRegistryProtoype(&ACLProxyAuth::UserRegistryEntry_, "proxy_auth");
ACLProxyAuth ACLProxyAuth::UserRegistryEntry_(new ACLUserData, "proxy_auth");
ACL::Prototype ACLProxyAuth::RegexRegistryProtoype(&ACLProxyAuth::RegexRegistryEntry_, "proxy_auth_regex" );
=== modified file 'src/URL.h'
--- src/URL.h 2015-01-13 07:25:36 +0000
+++ src/URL.h 2015-02-24 20:22:48 +0000
@@ -60,28 +60,59 @@
* and immutable, only settable at construction time,
*/
AnyP::UriScheme scheme_;
SBuf userInfo_; // aka 'URL-login'
};
class HttpRequest;
class HttpRequestMethod;
AnyP::ProtocolType urlParseProtocol(const char *, const char *e = NULL);
void urlInitialize(void);
HttpRequest *urlParse(const HttpRequestMethod&, char *, HttpRequest *request = NULL);
const char *urlCanonical(HttpRequest *);
char *urlCanonicalClean(const HttpRequest *);
const char *urlCanonicalFakeHttps(const HttpRequest * request);
bool urlIsRelative(const char *);
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);
void urlExtMethodConfigure(void);
#endif /* SQUID_SRC_URL_H_H */
=== modified file 'src/acl/DomainData.h'
--- src/acl/DomainData.h 2015-01-13 07:25:36 +0000
+++ src/acl/DomainData.h 2015-02-13 11:15:29 +0000
@@ -2,31 +2,31 @@
* 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_ACLDOMAINDATA_H
#define SQUID_ACLDOMAINDATA_H
#include "acl/Acl.h"
#include "acl/Data.h"
#include "splay.h"
class ACLDomainData : public ACLData<char const *>
{
MEMPROXY_CLASS(ACLDomainData);
public:
virtual ~ACLDomainData();
- bool match(char const *);
+ virtual bool match(char const *);
virtual SBufList dump() const;
void parse();
bool empty() const;
virtual ACLData<char const *> *clone() const;
Splay<char *> *domains;
};
#endif /* SQUID_ACLDOMAINDATA_H */
=== modified file 'src/acl/Makefile.am'
--- src/acl/Makefile.am 2015-02-02 20:02:55 +0000
+++ src/acl/Makefile.am 2015-02-09 16:37:08 +0000
@@ -94,40 +94,42 @@
Note.h \
Note.cc \
NoteData.h \
NoteData.cc \
PeerName.cc \
PeerName.h \
Protocol.cc \
ProtocolData.cc \
ProtocolData.h \
Protocol.h \
Random.cc \
Random.h \
Referer.cc \
Referer.h \
ReplyHeaderStrategy.h \
ReplyMimeType.cc \
ReplyMimeType.h \
RequestHeaderStrategy.h \
RequestMimeType.cc \
RequestMimeType.h \
+ ServerName.cc \
+ ServerName.h \
SourceAsn.h \
SourceDomain.cc \
SourceDomain.h \
SourceIp.cc \
SourceIp.h \
SquidError.h \
SquidError.cc \
SquidErrorData.cc \
SquidErrorData.h \
Tag.cc \
Tag.h \
Url.cc \
Url.h \
UrlLogin.cc \
UrlLogin.h \
UrlPath.cc \
UrlPath.h \
UrlPort.cc \
UrlPort.h \
UserData.cc \
=== added file 'src/acl/ServerName.cc'
--- src/acl/ServerName.cc 1970-01-01 00:00:00 +0000
+++ src/acl/ServerName.cc 2015-02-23 20:38:25 +0000
@@ -0,0 +1,117 @@
+/*
+ * 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_;
+
=== added file 'src/acl/ServerName.h'
--- src/acl/ServerName.h 1970-01-01 00:00:00 +0000
+++ src/acl/ServerName.h 2015-02-13 19:36:32 +0000
@@ -0,0 +1,62 @@
+/*
+ * 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;
+};
+
+/// \ingroup ACLAPI
+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 &);
+};
+
+/// \ingroup ACLAPI
+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 */
+
=== modified file 'src/cf.data.pre'
--- src/cf.data.pre 2015-01-31 16:34:08 +0000
+++ src/cf.data.pre 2015-02-24 20:23:51 +0000
@@ -1096,40 +1096,52 @@
acl aclname server_cert_fingerprint [-sha1] fingerprint
# match against server SSL certificate fingerprint [fast]
#
# The fingerprint is the digest of the DER encoded version
# of the whole certificate. The user should use the form: XX:XX:...
# Optional argument specifies the digest algorithm to use.
# The SHA1 digest algorithm is the default and is currently
# the only algorithm supported (-sha1).
acl aclname at_step step
# match against the current step during ssl_bump evaluation [fast]
# Never matches and should not be used outside the ssl_bump context.
#
# At each SslBump step, Squid evaluates ssl_bump directives to find
# the next bumping action (e.g., peek or splice). Valid SslBump step
# values and the corresponding ssl_bump evaluation moments are:
# 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 server_name .foo.com ...
+ # matches server name obtained from various sources [fast]
+ #
+ # The server name is obtained during SslBmp steps from such sources
+ # as CONNECT request URI, client SNI, and SSL server certificate CN.
+ # During each SslBump step, Squid may improve its understanding of a
+ # "true server name". Unlike dstdomain, this ACL does not perform
+ # DNS lookups.
+
+ acl aclname 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]
# The first matching ACL stops further ACL evaluation.
#
# ACLs from multiple any-of lines with the same name are ORed.
# For example, A = (a1 or a2) or (a3 or a4) can be written as
# acl A any-of a1 a2
# acl A any-of a3 a4
#
# This group ACL is fast if all evaluated ACLs in the group are fast
# and slow otherwise.
acl aclname all-of acl1 acl2 ...
# match all of the acls [fast or slow]
# The first mismatching ACL stops further ACL evaluation.
#
# ACLs from multiple all-of lines with the same name are ORed.
# For example, B = (b1 and b2) or (b3 and b4) can be written as
# acl B all-of b1 b2
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2015-02-06 19:45:02 +0000
+++ src/client_side.cc 2015-02-23 19:49:38 +0000
@@ -3679,40 +3679,48 @@
debugs(83, 3, "clientNegotiateSSL: FD " << fd << " negotiated cipher " <<
SSL_get_cipher(ssl));
client_cert = SSL_get_peer_certificate(ssl);
if (client_cert != NULL) {
debugs(83, 3, "clientNegotiateSSL: FD " << fd <<
" client certificate: subject: " <<
X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0));
debugs(83, 3, "clientNegotiateSSL: FD " << fd <<
" client certificate: issuer: " <<
X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0));
X509_free(client_cert);
} else {
debugs(83, 5, "clientNegotiateSSL: FD " << fd <<
" 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();
}
/**
* If SSL_CTX is given, starts reading the SSL handshake.
* Otherwise, calls switchToHttps to generate a dynamic SSL_CTX.
*/
static void
httpsEstablish(ConnStateData *connState, SSL_CTX *sslContext, Ssl::BumpMode bumpMode)
{
SSL *ssl = NULL;
assert(connState);
const Comm::ConnectionPointer &details = connState->clientConnection;
if (sslContext && !(ssl = httpsCreate(details, sslContext)))
return;
typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer,
connState, ConnStateData::requestTimeout);
@@ -3874,41 +3882,41 @@
debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd");
if (sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare)) {
doPeekAndSpliceStep();
SSL *ssl = fd_table[clientConnection->fd].ssl;
bool ret = Ssl::configureSSLUsingPkeyAndCertFromMemory(ssl, reply_message.getBody().c_str(), *port);
if (!ret)
debugs(33, 5, "Failed to set certificates to ssl object for PeekAndSplice mode");
} else {
SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port);
getSslContextDone(ctx, true);
}
return;
}
}
}
getSslContextDone(NULL);
}
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(port->signingCert.get());
certProperties.signWithX509.resetAndLock(port->signingCert.get());
if (port->signPkey.get())
certProperties.signWithPkey.resetAndLock(port->signPkey.get());
certProperties.signAlgorithm = Ssl::algSignTrusted;
return;
}
// In case of an error while connecting to the secure server, use a fake
// trusted certificate, with no mimicked fields and no adaptation
// algorithms. There is nothing we can mimic so we want to minimize the
// number of warnings the user will have to see to get to the error page.
assert(sslServerBump->entry);
if (sslServerBump->entry->isEmpty()) {
if (X509 *mimicCert = sslServerBump->serverCert.get())
certProperties.mimicCert.resetAndLock(mimicCert);
@@ -4095,41 +4103,41 @@
// bumped intercepted conns should already have Config.Timeout.request set
// but forwarded connections may only have Config.Timeout.lifetime. [Re]set
// to make sure the connection does not get stuck on non-SSL clients.
typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer,
this, ConnStateData::requestTimeout);
commSetConnTimeout(clientConnection, Config.Timeout.request, timeoutCall);
// Disable the client read handler until CachePeer selection is complete
Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientNegotiateSSL, this, 0);
switchedToHttps_ = true;
}
void
ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode)
{
assert(!switchedToHttps_);
sslConnectHostOrIp = request->GetHost();
- sslCommonName = request->GetHost();
+ resetSslCommonName(request->GetHost());
// We are going to read new request
flags.readMore = true;
debugs(33, 5, HERE << "converting " << clientConnection << " to SSL");
// keep version major.minor details the same.
// but we are now performing the HTTPS handshake traffic
transferProtocol.protocol = AnyP::PROTO_HTTPS;
// If sslServerBump is set, then we have decided to deny CONNECT
// and now want to switch to SSL to send the error to the client
// without even peeking at the origin server certificate.
if (bumpServerMode == Ssl::bumpServerFirst && !sslServerBump) {
request->flags.sslPeek = true;
sslServerBump = new Ssl::ServerBump(request);
// will call httpsPeeked() with certificate and connection, eventually
FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request.getRaw());
return;
} else if (bumpServerMode == Ssl::bumpPeek || bumpServerMode == Ssl::bumpStare) {
@@ -4172,42 +4180,44 @@
int ret = 0;
if ((ret = Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) < 0)
debugs(83, 2, "SSL_accept failed.");
BIO *b = SSL_get_rbio(ssl);
assert(b);
Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
if (ret < 0) {
const err_type err = bio->noSslClient() ? ERR_PROTOCOL_UNKNOWN : ERR_SECURE_ACCEPT_FAIL;
if (!conn->spliceOnError(err))
conn->clientConnection->close();
return;
}
if (bio->rBufData().contentSize() > 0)
conn->receivedFirstByte();
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!!! ");
Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
conn->startPeekAndSpliceDone();
return;
}
}
void ConnStateData::startPeekAndSplice()
{
// will call httpsPeeked() with certificate and connection, eventually
SSL_CTX *unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port);
fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX;
if (!httpsCreate(clientConnection, unConfiguredCTX))
return;
// commSetConnTimeout() was called for this request before we switched.
@@ -4328,64 +4338,45 @@
ConnStateData::doPeekAndSpliceStep()
{
SSL *ssl = fd_table[clientConnection->fd].ssl;
BIO *b = SSL_get_rbio(ssl);
assert(b);
Ssl::ClientBio *bio = static_cast<Ssl::ClientBio *>(b->ptr);
debugs(33, 5, "PeekAndSplice mode, proceed with client negotiation. Currrent state:" << SSL_state_string_long(ssl));
bio->hold(false);
Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, this, 0);
switchedToHttps_ = true;
}
void
ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection)
{
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)
currentobject->http->request->detailError(sslServerBump->request->errType, sslServerBump->request->errDetail);
}
getSslContextStart();
}
#endif /* USE_OPENSSL */
/// check FD after clientHttp[s]ConnectionOpened, adjust HttpSockets as needed
static bool
OpenedHttpSocket(const Comm::ConnectionPointer &c, const Ipc::FdNoteId portType)
{
if (!Comm::IsConnOpen(c)) {
Must(NHttpSockets > 0); // we tried to open some
--NHttpSockets; // there will be fewer sockets than planned
Must(HttpSockets[NHttpSockets] < 0); // no extra fds received
=== modified file 'src/client_side.h'
--- src/client_side.h 2015-01-16 18:12:04 +0000
+++ src/client_side.h 2015-02-13 16:50:56 +0000
@@ -363,40 +363,42 @@
/**
* Done create dynamic ssl certificate.
*
* \param[in] isNew if generated certificate is new, so we need to add this certificate to storage.
*/
void getSslContextDone(SSL_CTX * sslContext, bool isNew = false);
/// Callback function. It is called when squid receive message from ssl_crtd.
static void sslCrtdHandleReplyWrapper(void *data, const Helper::Reply &reply);
/// Proccess response from ssl_crtd.
void sslCrtdHandleReply(const Helper::Reply &reply);
void switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode);
bool switchedToHttps() const { return switchedToHttps_; }
Ssl::ServerBump *serverBump() {return sslServerBump;}
inline void setServerBump(Ssl::ServerBump *srvBump) {
if (!sslServerBump)
sslServerBump = srvBump;
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);
/// Called when the client sends the first request on a bumped connection.
/// Returns false if no [delayed] error should be written to the client.
/// Otherwise, writes the error to the client and returns true. Also checks
/// for SQUID_X509_V_ERR_DOMAIN_MISMATCH on bumped requests.
bool serveDelayedError(ClientSocketContext *context);
Ssl::BumpMode sslBumpMode; ///< ssl_bump decision (Ssl::bumpEnd if n/a).
#else
bool switchedToHttps() const { return false; }
#endif
/* clt_conn_tag=tag annotation access */
const SBuf &connectionTag() const { return connectionTag_; }
void connectionTag(const char *aTag) { connectionTag_ = aTag; }
/// handle a control message received by context from a peer and call back
@@ -454,41 +456,41 @@
bool parseProxyProtocolHeader();
bool parseProxy1p0();
bool parseProxy2p0();
bool proxyProtocolError(const char *reason);
/// whether PROXY protocol header is still expected
bool needProxyProtocolHeader_;
#if USE_AUTH
/// some user details that can be used to perform authentication on this connection
Auth::UserRequest::Pointer auth_;
#endif
/// the parser state for current HTTP/1.x input buffer processing
Http1::RequestParserPointer parser_;
#if USE_OPENSSL
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
Ssl::ServerBump *sslServerBump;
Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use
#endif
/// the reason why we no longer write the response or nil
const char *stoppedSending_;
/// the reason why we no longer read the request or nil
const char *stoppedReceiving_;
AsyncCall::Pointer reader; ///< set when we are reading
bool receivedFirstByte_; ///< true if at least one byte received on this connection
SBuf connectionTag_; ///< clt_conn_tag=Tag annotation for client connection
};
void setLogUri(ClientHttpRequest * http, char const *uri, bool cleanUrl = false);
=== modified file 'src/ssl/PeerConnector.cc'
--- src/ssl/PeerConnector.cc 2015-02-09 18:12:05 +0000
+++ src/ssl/PeerConnector.cc 2015-02-23 19:59:32 +0000
@@ -28,41 +28,42 @@
#include "ssl/helper.h"
#include "ssl/PeerConnector.h"
#include "ssl/ServerBump.h"
#include "ssl/support.h"
CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeerConnector);
Ssl::PeerConnector::PeerConnector(
HttpRequestPointer &aRequest,
const Comm::ConnectionPointer &aServerConn,
const Comm::ConnectionPointer &aClientConn,
AsyncCall::Pointer &aCallback,
const time_t timeout):
AsyncJob("Ssl::PeerConnector"),
request(aRequest),
serverConn(aServerConn),
clientConn(aClientConn),
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()));
}
Ssl::PeerConnector::~PeerConnector()
{
debugs(83, 5, "Peer connector " << this << " gone");
}
bool Ssl::PeerConnector::doneAll() const
{
return (!callback || callback->canceled()) && AsyncJob::doneAll();
}
/// Preps connection and SSL state. Calls negotiate().
void
Ssl::PeerConnector::start()
{
AsyncJob::start();
@@ -243,51 +244,76 @@
SSL *ssl = fd_table[fd].ssl;
const int result = SSL_connect(ssl);
if (result <= 0) {
handleNegotiateError(result);
return; // we might be gone by now
}
if (serverConnection()->getPeer() && !SSL_session_reused(ssl)) {
if (serverConnection()->getPeer()->sslSession)
SSL_SESSION_free(serverConnection()->getPeer()->sslSession);
serverConnection()->getPeer()->sslSession = SSL_get1_session(ssl);
}
if (!sslFinalized())
return;
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, HERE << "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);
}
}
if (Ssl::TheConfig.ssl_crt_validator) {
Ssl::CertValidationRequest validationRequest;
// WARNING: Currently we do not use any locking for any of the
// members of the Ssl::CertValidationRequest class. In this code the
// Ssl::CertValidationRequest object used only to pass data to
// Ssl::CertValidationHelper::submit method.
validationRequest.ssl = ssl;
validationRequest.domainName = request->GetHost();
if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(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 {
@@ -307,50 +333,49 @@
}
serverConn->close();
return true;
}
}
return true;
}
void switchToTunnel(HttpRequest *request, Comm::ConnectionPointer & clientConn, Comm::ConnectionPointer &srvConn);
void
Ssl::PeerConnector::cbCheckForPeekAndSpliceDone(allow_t answer, void *data)
{
Ssl::PeerConnector *peerConnect = (Ssl::PeerConnector *) data;
peerConnect->checkForPeekAndSpliceDone((Ssl::BumpMode)answer.kind);
}
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);
acl_checklist->nonBlockingCheck(Ssl::PeerConnector::cbCheckForPeekAndSpliceDone, this);
}
void
Ssl::PeerConnector::checkForPeekAndSpliceDone(Ssl::BumpMode const action)
{
SSL *ssl = fd_table[serverConn->fd].ssl;
BIO *b = SSL_get_rbio(ssl);
Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
debugs(83,5, "Will check for peek and splice on FD " << serverConn->fd);
Ssl::BumpMode finalAction = action;
// adjust the final bumping mode if needed
if (finalAction < Ssl::bumpSplice)
finalAction = Ssl::bumpBump;
if (finalAction == Ssl::bumpSplice && !srvBio->canSplice())
=== modified file 'src/ssl/PeerConnector.h'
--- src/ssl/PeerConnector.h 2015-01-13 07:25:36 +0000
+++ src/ssl/PeerConnector.h 2015-02-23 19:48:06 +0000
@@ -139,45 +139,50 @@
private:
PeerConnector(const PeerConnector &); // not implemented
PeerConnector &operator =(const PeerConnector &); // not implemented
/// mimics FwdState to minimize changes to FwdState::initiate/negotiateSsl
Comm::ConnectionPointer const &serverConnection() const { return serverConn; }
void bail(ErrorState *error); ///< Return an error to the PeerConnector caller
/// Callback the caller class, and pass the ready to communicate secure
/// connection or an error if PeerConnector failed.
void callBack();
/// Process response from cert validator helper
void sslCrtvdHandleReply(Ssl::CertValidationResponse const &);
/// 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 &);
/// A wrapper function for negotiateSsl for use with Comm::SetSelect
static void NegotiateSsl(int fd, void *data);
/// A wrapper function for checkForPeekAndSpliceDone for use with acl
static void cbCheckForPeekAndSpliceDone(allow_t answer, void *data);
HttpRequestPointer request; ///< peer connection trigger or cause
Comm::ConnectionPointer serverConn; ///< TCP connection to the peer
Comm::ConnectionPointer clientConn; ///< TCP connection to the client
AsyncCall::Pointer callback; ///< we call this with the results
AsyncCall::Pointer closeHandler; ///< we call this when the connection closed
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
};
std::ostream &operator <<(std::ostream &os, const Ssl::PeerConnectorAnswer &a);
} // namespace Ssl
#endif /* SQUID_PEER_CONNECTOR_H */
=== modified file 'src/url.cc'
--- src/url.cc 2015-01-13 07:25:36 +0000
+++ src/url.cc 2015-02-24 12:24:35 +0000
@@ -666,64 +666,42 @@
urlbuf[urllen] = '/';
++urllen;
strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
} else {
++last_slash;
size_t pathlen = last_slash - path;
if (pathlen > MAX_URL - urllen - 1) {
pathlen = MAX_URL - urllen - 1;
}
strncpy(&urlbuf[urllen], path, pathlen);
urllen += pathlen;
if (urllen + 1 < MAX_URL) {
strncpy(&urlbuf[urllen], relUrl, MAX_URL - urllen - 1);
}
}
}
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;
while ('.' == *h)
++h;
hl = strlen(h);
dl = strlen(d);
/*
* Start at the ends of the two strings and work towards the
* beginning.
*/
while (xtolower(h[--hl]) == xtolower(d[--dl])) {
if (hl == 0 && dl == 0) {
/*
* We made it all the way to the beginning of both
* strings without finding any difference.
@@ -746,40 +724,47 @@
return -1;
}
if (0 == dl) {
/*
* The domain string is shorter than the host string.
* This is a match only if the first domain character
* is a leading '.'.
*/
if ('.' == d[0])
return 0;
else
return 1;
}
}
/*
* 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
* be greater than ".foo.com" even though '-' is less than '.'.
*/
if ('.' == d[dl])
return 1;
if ('.' == h[hl])
return -1;
return (xtolower(h[hl]) - xtolower(d[dl]));
}
/*
* return true if we can serve requests for this method.
*/
int
urlCheckRequest(const HttpRequest * r)
{
_______________________________________________
squid-dev mailing list
[email protected]
http://lists.squid-cache.org/listinfo/squid-dev