SSL server certificate fingerprint ACL type

This patch add the "server_ssl_cert_fingerprint" acl type to match
against server SSL certificate fingerprint.
The new acl type has the form:
  acl aclname server_ssl_cert_fingerprint [-sha1] fingerprint1 ...

The fingerprint must given in the form:
    XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
where X are any valid hexadecimal number

Example usage:
acl BrokeServer dst 192.168.1.23
acl GoodCert server_ssl_cert_fingerprint
AB:2A:82:AF:46:AE:1F:31:21:74:65:BF:56:47:25:D1:87:51:41:AE
sslproxy_cert_error allow BrokeServer GoodCert
sslproxy_cert_error deny all

Someone can retrieve the fingerprint of a certificate using the openssl
command:
  # openssl x509 -fingerprint -in test.pem -noout
  # openssl s_client -host www.paypal.com -port 443 2> /dev/null |
openssl x509 -fingerprint   -noout


This is a Measurement Factory project
SSL server certificate fingerprint ACL type

This patch add the "server_ssl_cert_fingerprint" acl type to match against
server SSL certificate fingerprint.
The new acl type has the form:
  acl aclname server_ssl_cert_fingerprint [-sha1] fingerprint1 ...

The fingerprint must given in the form:
    XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX
where X are any valid hexadecimal number

Example usage:
acl BrokeServer dst 192.168.1.23
acl GoodCert server_ssl_cert_fingerprint AB:2A:82:AF:46:AE:1F:31:21:74:65:BF:56:47:25:D1:87:51:41:AE
sslproxy_cert_error allow BrokeServer GoodCert
sslproxy_cert_error deny all

=== modified file 'src/AclRegs.cc'
--- src/AclRegs.cc	2012-10-29 01:31:29 +0000
+++ src/AclRegs.cc	2012-11-13 18:18:05 +0000
@@ -42,40 +42,43 @@
 #include "acl/Protocol.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"
 #if USE_SSL
 #include "acl/SslErrorData.h"
 #include "acl/SslError.h"
 #include "acl/CertificateData.h"
 #include "acl/Certificate.h"
 #endif
 #include "acl/Strategised.h"
 #include "acl/Strategy.h"
 #include "acl/StringData.h"
+#if USE_SSL
+#include "acl/ServerCertificate.h"
+#endif
 #include "acl/Tag.h"
 #include "acl/TimeData.h"
 #include "acl/Time.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/AclProxyAuth.h"
 #include "auth/AclMaxUserIp.h"
 #endif
 #if USE_IDENT
 #include "ident/AclIdent.h"
 #endif
 
 ACL::Prototype ACLBrowser::RegistryProtoype(&ACLBrowser::RegistryEntry_, "browser");
 ACLStrategised<char const *> ACLBrowser::RegistryEntry_(new ACLRegexData, ACLRequestHeaderStrategy<HDR_USER_AGENT>::Instance(), "browser");
 ACL::Prototype ACLDestinationDomain::LiteralRegistryProtoype(&ACLDestinationDomain::LiteralRegistryEntry_, "dstdomain");
 ACLStrategised<char const *> ACLDestinationDomain::LiteralRegistryEntry_(new ACLDomainData, ACLDestinationDomainStrategy::Instance(), "dstdomain");
@@ -126,43 +129,45 @@
 ACL::Prototype ACLSourceDomain::RegexRegistryProtoype(&ACLSourceDomain::RegexRegistryEntry_, "srcdom_regex");
 ACLStrategised<char const *> ACLSourceDomain::RegexRegistryEntry_(new ACLRegexData,ACLSourceDomainStrategy::Instance() ,"srcdom_regex");
 ACL::Prototype ACLSourceIP::RegistryProtoype(&ACLSourceIP::RegistryEntry_, "src");
 ACLSourceIP ACLSourceIP::RegistryEntry_;
 ACL::Prototype ACLTime::RegistryProtoype(&ACLTime::RegistryEntry_, "time");
 ACLStrategised<time_t> ACLTime::RegistryEntry_(new ACLTimeData, ACLTimeStrategy::Instance(), "time");
 ACL::Prototype ACLUrl::RegistryProtoype(&ACLUrl::RegistryEntry_, "url_regex");
 ACLStrategised<char const *> ACLUrl::RegistryEntry_(new ACLRegexData, ACLUrlStrategy::Instance(), "url_regex");
 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_SSL
 ACL::Prototype ACLSslError::RegistryProtoype(&ACLSslError::RegistryEntry_, "ssl_error");
 ACLStrategised<const Ssl::Errors *> ACLSslError::RegistryEntry_(new ACLSslErrorData, ACLSslErrorStrategy::Instance(), "ssl_error");
 ACL::Prototype ACLCertificate::UserRegistryProtoype(&ACLCertificate::UserRegistryEntry_, "user_cert");
-ACLStrategised<SSL *> ACLCertificate::UserRegistryEntry_(new ACLCertificateData (sslGetUserAttribute), ACLCertificateStrategy::Instance(), "user_cert");
+ACLStrategised<X509 *> ACLCertificate::UserRegistryEntry_(new ACLCertificateData (Ssl::GetX509UserAttribute, "*"), ACLCertificateStrategy::Instance(), "user_cert");
 ACL::Prototype ACLCertificate::CARegistryProtoype(&ACLCertificate::CARegistryEntry_, "ca_cert");
-ACLStrategised<SSL *> ACLCertificate::CARegistryEntry_(new ACLCertificateData (sslGetCAAttribute), ACLCertificateStrategy::Instance(), "ca_cert");
+ACLStrategised<X509 *> ACLCertificate::CARegistryEntry_(new ACLCertificateData (Ssl::GetX509CAAttribute, "*"), ACLCertificateStrategy::Instance(), "ca_cert");
+ACL::Prototype ACLServerCertificate::X509FingerprintRegistryProtoype(&ACLServerCertificate::X509FingerprintRegistryEntry_, "server_ssl_cert_fingerprint");
+ACLStrategised<X509 *> ACLServerCertificate::X509FingerprintRegistryEntry_(new ACLCertificateData(Ssl::GetX509Fingerprint, "-sha1", true), ACLServerCertificateStrategy::Instance(), "server_ssl_cert_fingerprint");
 #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/acl/Certificate.cc'
--- src/acl/Certificate.cc	2012-09-01 14:38:36 +0000
+++ src/acl/Certificate.cc	2012-11-13 18:18:05 +0000
@@ -36,32 +36,35 @@
 
 /* MS Visual Studio Projects are monolithic, so we need the following
  * #if to exclude the SSL code from compile process when not needed.
  */
 #if USE_SSL
 
 #include "acl/Certificate.h"
 #include "acl/Checklist.h"
 #include "acl/CertificateData.h"
 #include "HttpRequest.h"
 #include "client_side.h"
 #include "fde.h"
 #include "globals.h"
 
 int
 ACLCertificateStrategy::match (ACLData<MatchType> * &data, ACLFilledChecklist *checklist)
 {
     const int fd = checklist->fd();
     const bool goodDescriptor = 0 <= fd && fd <= Biggest_FD;
     SSL *ssl = goodDescriptor ? fd_table[fd].ssl : 0;
-    return data->match (ssl);
+    X509 *cert = SSL_get_peer_certificate(ssl);
+    const bool res = data->match (cert);
+    X509_free(cert);
+    return res;
 }
 
 ACLCertificateStrategy *
 ACLCertificateStrategy::Instance()
 {
     return &Instance_;
 }
 
 ACLCertificateStrategy ACLCertificateStrategy::Instance_;
 
 #endif /* USE_SSL */

=== modified file 'src/acl/Certificate.h'
--- src/acl/Certificate.h	2012-09-01 14:38:36 +0000
+++ src/acl/Certificate.h	2012-11-13 18:18:05 +0000
@@ -23,50 +23,50 @@
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  *
  * Copyright (c) 2003, Robert Collins <robe...@squid-cache.org>
  */
 
 #ifndef SQUID_ACLCERTIFICATE_H
 #define SQUID_ACLCERTIFICATE_H
 
 #include "acl/Acl.h"
 #include "acl/Data.h"
 #include "acl/Checklist.h"
 #include "ssl/support.h"
 #include "acl/Strategised.h"
 
 /// \ingroup ACLAPI
-class ACLCertificateStrategy : public ACLStrategy<SSL *>
+class ACLCertificateStrategy : public ACLStrategy<X509 *>
 {
 
 public:
     virtual int match (ACLData<MatchType> * &, ACLFilledChecklist *);
     static ACLCertificateStrategy *Instance();
     /* Not implemented to prevent copies of the instance. */
     /* Not private to prevent brain dead g+++ warnings about
      * private constructors with no friends */
     ACLCertificateStrategy(ACLCertificateStrategy const &);
 
 private:
     static ACLCertificateStrategy Instance_;
     ACLCertificateStrategy() {}
 
     ACLCertificateStrategy&operator=(ACLCertificateStrategy const &);
 };
 
 /// \ingroup ACLAPI
 class ACLCertificate
 {
 
 private:
     static ACL::Prototype UserRegistryProtoype;
-    static ACLStrategised<SSL*> UserRegistryEntry_;
+    static ACLStrategised<X509*> UserRegistryEntry_;
     static ACL::Prototype CARegistryProtoype;
-    static ACLStrategised<SSL *> CARegistryEntry_;
+    static ACLStrategised<X509 *> CARegistryEntry_;
 };
 
 #endif /* SQUID_ACLCERTIFICATE_H */

=== modified file 'src/acl/CertificateData.cc'
--- src/acl/CertificateData.cc	2012-09-01 14:38:36 +0000
+++ src/acl/CertificateData.cc	2012-11-13 21:13:25 +0000
@@ -18,116 +18,160 @@
  *  it under the terms of the GNU General Public License as published by
  *  the Free Software Foundation; either version 2 of the License, or
  *  (at your option) any later version.
  *
  *  This program is distributed in the hope that it will be useful,
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  *
  * Copyright (c) 2003, Robert Collins <robe...@squid-cache.org>
  */
 
 #include "squid.h"
 #include "acl/CertificateData.h"
 #include "acl/Checklist.h"
+#include "Debug.h"
 #include "cache_cf.h"
 #include "wordlist.h"
 
-ACLCertificateData::ACLCertificateData(SSLGETATTRIBUTE *sslStrategy) : attribute (NULL), values (), sslAttributeCall (sslStrategy)
-{}
+ACLCertificateData::ACLCertificateData(Ssl::GETX509ATTRIBUTE *sslStrategy, const char *attrs, bool optionalAttr) : validAttributesStr(attrs), attributeIsOptional(optionalAttr), attribute (NULL), values (), sslAttributeCall (sslStrategy)
+{
+    if (attrs) {
+        size_t current;
+        size_t next = -1;
+        std::string valid(attrs);
+        do {
+            current = next + 1;
+            next = valid.find_first_of( "|", current);
+            validAttributes.push_back(valid.substr( current, next - current ));
+        } while (next != std::string::npos);
+    }
+}
 
 ACLCertificateData::ACLCertificateData(ACLCertificateData const &old) : attribute (NULL), values (old.values), sslAttributeCall (old.sslAttributeCall)
 {
+    validAttributesStr = old.validAttributesStr;
+    validAttributes.assign (old.validAttributes.begin(), old.validAttributes.end());
+    attributeIsOptional = old.attributeIsOptional;
     if (old.attribute)
         attribute = xstrdup (old.attribute);
 }
 
 template<class T>
 inline void
 xRefFree(T &thing)
 {
     xfree (thing);
 }
 
 ACLCertificateData::~ACLCertificateData()
 {
     safe_free (attribute);
 }
 
 template<class T>
 inline int
 splaystrcmp (T&l, T&r)
 {
     return strcmp ((char *)l,(char *)r);
 }
 
 bool
-ACLCertificateData::match(SSL *ssl)
+ACLCertificateData::match(X509 *cert)
 {
-    if (!ssl)
+    if (!cert)
         return 0;
 
-    char const *value = sslAttributeCall(ssl, attribute);
-
+    char const *value = sslAttributeCall(cert, attribute);
+    debugs(28, 6, HERE << (attribute ? attribute : "value") << "=" << value);
     if (value == NULL)
         return 0;
 
     return values.match(value);
 }
 
 static void
 aclDumpAttributeListWalkee(char * const & node_data, void *outlist)
 {
     /* outlist is really a wordlist ** */
     wordlistAdd((wordlist **)outlist, node_data);
 }
 
 wordlist *
 ACLCertificateData::dump()
 {
     wordlist *wl = NULL;
-    wordlistAdd(&wl, attribute);
+    if (validAttributesStr)
+        wordlistAdd(&wl, attribute);
     /* damn this is VERY inefficient for long ACL lists... filling
      * a wordlist this way costs Sum(1,N) iterations. For instance
      * a 1000-elements list will be filled in 499500 iterations.
      */
     /* XXX FIXME: don't break abstraction */
     values.values->walk(aclDumpAttributeListWalkee, &wl);
     return wl;
 }
 
 void
 ACLCertificateData::parse()
 {
-    char *newAttribute = strtokFile();
+    if (validAttributesStr) {
+        char *newAttribute = strtokFile();
 
-    if (!newAttribute)
-        self_destruct();
+        if (!newAttribute) {
+            if (attributeIsOptional)
+                return;
 
-    /* an acl must use consistent attributes in all config lines */
-    if (attribute) {
-        if (strcasecmp(newAttribute, attribute) != 0)
+            debugs(28, DBG_CRITICAL, "required attribute argument missing");
             self_destruct();
-    } else
-        attribute = xstrdup(newAttribute);
+        }
+
+        // Handle the cases where we have optional -x type attributes
+        if (attributeIsOptional && newAttribute[0] != '-')
+            // The read token is not an attribute/option, so add it to values list
+            values.insert(newAttribute);
+        else {
+            bool valid = false;
+            for (std::list<std::string>::const_iterator it = validAttributes.begin(); it != validAttributes.end(); ++it) {
+                if (*it == "*" || *it == newAttribute) {
+                    valid = true;
+                    break;
+                }
+            }
+
+            if (!valid) {
+                debugs(28, DBG_CRITICAL, "Unknown option. Supported option(s) are: " << validAttributesStr);
+                self_destruct();
+            }
+            
+            /* an acl must use consistent attributes in all config lines */
+            if (attribute) {
+                if (strcasecmp(newAttribute, attribute) != 0) {
+                    debugs(28, DBG_CRITICAL, "An acl must use consistent attributes in all config lines (" << newAttribute << "!=" << attribute << ").");
+                    self_destruct();
+                }
+            } else
+                attribute = xstrdup(newAttribute);
+        }
+    }
 
     values.parse();
 }
 
 bool
 ACLCertificateData::empty() const
 {
     return values.empty();
 }
 
-ACLData<SSL *> *
+ACLData<X509 *> *
 ACLCertificateData::clone() const
 {
     /* Splay trees don't clone yet. */
     return new ACLCertificateData(*this);
 }

=== modified file 'src/acl/CertificateData.h'
--- src/acl/CertificateData.h	2012-09-01 14:38:36 +0000
+++ src/acl/CertificateData.h	2012-11-13 18:18:05 +0000
@@ -21,48 +21,60 @@
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  *  GNU General Public License for more details.
  *
  *  You should have received a copy of the GNU General Public License
  *  along with this program; if not, write to the Free Software
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  *
  * Copyright (c) 2003, Robert Collins <robe...@squid-cache.org>
  */
 
 #ifndef SQUID_ACLCERTIFICATEDATA_H
 #define SQUID_ACLCERTIFICATEDATA_H
 
 #include "splay.h"
 #include "acl/Acl.h"
 #include "acl/Data.h"
 #include "ssl/support.h"
 #include "acl/StringData.h"
+#include <string>
+#include <list>
 
 /// \ingroup ACLAPI
-class ACLCertificateData : public ACLData<SSL *>
+class ACLCertificateData : public ACLData<X509 *>
 {
 
 public:
     MEMPROXY_CLASS(ACLCertificateData);
 
-    ACLCertificateData(SSLGETATTRIBUTE *);
+    ACLCertificateData(Ssl::GETX509ATTRIBUTE *, const char *attributes, bool optionalAttr = false);
     ACLCertificateData(ACLCertificateData const &);
     ACLCertificateData &operator= (ACLCertificateData const &);
     virtual ~ACLCertificateData();
-    bool match(SSL *);
+    bool match(X509 *);
     wordlist *dump();
     void parse();
     bool empty() const;
-    virtual ACLData<SSL *> *clone() const;
+    virtual ACLData<X509 *> *clone() const;
 
+    /// A '|'-delimited list of valid ACL attributes.
+    /// A "*" item means that any attribute is acceptable.
+    /// Assumed to be a const-string and is never duped/freed.
+    /// Nil unless ACL form is: acl Name type attribute value1 ...
+    const char *validAttributesStr;
+    /// Parsed list of valid attribute names
+    std::list<std::string> validAttributes;
+    /// True if the attribute is optional (-xxx options)
+    bool attributeIsOptional;
     char *attribute;
     ACLStringData values;
 
 private:
-    SSLGETATTRIBUTE *sslAttributeCall;
+    /// The callback used to retrieve the data from X509 cert
+    Ssl::GETX509ATTRIBUTE *sslAttributeCall;
 };
 
 MEMPROXY_CLASS_INLINE(ACLCertificateData);
 
 #endif /* SQUID_ACLCERTIFICATEDATA_H */

=== modified file 'src/acl/FilledChecklist.h'
--- src/acl/FilledChecklist.h	2012-10-03 07:34:10 +0000
+++ src/acl/FilledChecklist.h	2012-11-13 18:18:05 +0000
@@ -56,40 +56,42 @@
     Ip::Address src_addr;
     Ip::Address dst_addr;
     Ip::Address my_addr;
     CachePeer *dst_peer;
     char *dst_rdns;
 
     HttpRequest *request;
     HttpReply *reply;
 
     char rfc931[USER_IDENT_SZ];
 #if USE_AUTH
     Auth::UserRequest::Pointer auth_user_request;
 #endif
 #if SQUID_SNMP
     char *snmp_community;
 #endif
 
 #if USE_SSL
     /// SSL [certificate validation] errors, in undefined order
     Ssl::Errors *sslErrors;
+    /// The peer certificate
+    Ssl::X509_Pointer serverCert;
 #endif
 
     ExternalACLEntry *extacl_entry;
 
 private:
     ConnStateData * conn_;          /**< hack for ident and NTLM */
     int fd_;                        /**< may be available when conn_ is not */
     bool destinationDomainChecked_;
     bool sourceDomainChecked_;
     /// not implemented; will cause link failures if used
     ACLFilledChecklist(const ACLFilledChecklist &);
     /// not implemented; will cause link failures if used
     ACLFilledChecklist &operator=(const ACLFilledChecklist &);
 
     CBDATA_CLASS(ACLFilledChecklist);
 };
 
 /// convenience and safety wrapper for dynamic_cast<ACLFilledChecklist*>
 inline
 ACLFilledChecklist *Filled(ACLChecklist *checklist)

=== modified file 'src/acl/Makefile.am'
--- src/acl/Makefile.am	2012-10-29 01:31:29 +0000
+++ src/acl/Makefile.am	2012-11-13 18:18:05 +0000
@@ -105,39 +105,41 @@
 	UrlPort.cc \
 	UrlPort.h \
 	UserData.cc \
 	UserData.h \
 	AclNameList.h \
 	AclDenyInfoList.h \
 	Gadgets.cc \
 	Gadgets.h \
 	AclSizeLimit.h
 
 ## Add conditional sources
 ## TODO: move these to their respectful dirs when those dirs are created
 
 EXTRA_libacls_la_SOURCES =
 
 SSL_ACLS = \
         CertificateData.cc \
         CertificateData.h  \
         Certificate.cc \
         Certificate.h  \
+	ServerCertificate.cc \
+	ServerCertificate.h \
         SslError.cc \
         SslError.h \
         SslErrorData.cc \
         SslErrorData.h
 
 if ENABLE_SSL
 libacls_la_SOURCES += $(SSL_ACLS)
 endif
 
 EXTRA_libacls_la_SOURCES += $(SSL_ACLS)
 
 
 ARP_ACLS = Arp.cc Arp.h Eui64.cc Eui64.h
 
 if USE_SQUID_EUI
 libacls_la_SOURCES += $(ARP_ACLS)
 endif
 
 EXTRA_libacls_la_SOURCES += $(ARP_ACLS)

=== added file 'src/acl/ServerCertificate.cc'
--- src/acl/ServerCertificate.cc	1970-01-01 00:00:00 +0000
+++ src/acl/ServerCertificate.cc	2012-11-13 17:28:30 +0000
@@ -0,0 +1,41 @@
+/*
+ * $Id$
+ *
+ */
+
+#include "squid.h"
+
+#if USE_SSL
+
+#include "acl/ServerCertificate.h"
+#include "acl/Checklist.h"
+#include "acl/CertificateData.h"
+#include "fde.h"
+#include "client_side.h"
+#include "ssl/ServerBump.h"
+
+
+int
+ACLServerCertificateStrategy::match (ACLData<MatchType> * &data, ACLFilledChecklist *checklist)
+{
+    X509 *cert = NULL;
+    if (checklist->serverCert.get())
+        cert = checklist->serverCert.get();
+    else if (checklist->conn() != NULL && checklist->conn()->serverBump())
+        cert = checklist->conn()->serverBump()->serverCert.get();
+
+    if (!cert)
+        return 0;
+
+    return data->match(cert);
+}
+
+ACLServerCertificateStrategy *
+ACLServerCertificateStrategy::Instance()
+{
+    return &Instance_;
+}
+
+ACLServerCertificateStrategy ACLServerCertificateStrategy::Instance_;
+
+#endif /* USE_SSL */

=== added file 'src/acl/ServerCertificate.h'
--- src/acl/ServerCertificate.h	1970-01-01 00:00:00 +0000
+++ src/acl/ServerCertificate.h	2012-11-13 17:28:30 +0000
@@ -0,0 +1,41 @@
+/*
+ * $Id$
+ */
+
+#ifndef SQUID_ACLSERVERCERTIFICATE_H
+#define SQUID_ACLSERVERCERTIFICATE_H
+
+#include "acl/Acl.h"
+#include "acl/Data.h"
+#include "acl/Checklist.h"
+#include "ssl/support.h"
+#include "acl/Strategised.h"
+
+/// \ingroup ACLAPI
+class ACLServerCertificateStrategy : public ACLStrategy<X509 *>
+{
+
+public:
+    virtual int match (ACLData<MatchType> * &, ACLFilledChecklist *);
+    static ACLServerCertificateStrategy *Instance();
+    /* Not implemented to prevent copies of the instance. */
+    /* Not private to prevent brain dead g+++ warnings about
+     * private constructors with no friends */
+    ACLServerCertificateStrategy(ACLServerCertificateStrategy const &);
+
+private:
+    static ACLServerCertificateStrategy Instance_;
+    ACLServerCertificateStrategy() {}
+
+    ACLServerCertificateStrategy&operator=(ACLServerCertificateStrategy const &);
+};
+
+/// \ingroup ACLAPI
+class ACLServerCertificate
+{
+private:
+    static ACL::Prototype X509FingerprintRegistryProtoype;
+    static ACLStrategised<X509*> X509FingerprintRegistryEntry_;
+};
+
+#endif /* SQUID_ACLSERVERCERTIFICATE_H */

=== modified file 'src/acl/StringData.cc'
--- src/acl/StringData.cc	2012-09-01 14:38:36 +0000
+++ src/acl/StringData.cc	2012-11-13 18:18:05 +0000
@@ -49,40 +49,46 @@
 
 template<class T>
 inline void
 xRefFree(T &thing)
 {
     xfree (thing);
 }
 
 ACLStringData::~ACLStringData()
 {
     if (values)
         values->destroy(xRefFree);
 }
 
 static int
 splaystrcmp (char * const &l, char * const &r)
 {
     return strcmp (l,r);
 }
 
+void
+ACLStringData::insert(const char *value)
+{
+    values = values->insert(xstrdup(value), splaystrcmp);
+}
+
 bool
 ACLStringData::match(char const *toFind)
 {
     if (!values || !toFind)
         return 0;
 
     debugs(28, 3, "aclMatchStringList: checking '" << toFind << "'");
 
     values = values->splay((char *)toFind, splaystrcmp);
 
     debugs(28, 3, "aclMatchStringList: '" << toFind << "' " << (splayLastResult ? "NOT found" : "found"));
 
     return !splayLastResult;
 }
 
 static void
 aclDumpStringWalkee(char * const & node_data, void *outlist)
 {
     /* outlist is really a wordlist ** */
     wordlistAdd((wordlist **)outlist, node_data);

=== modified file 'src/acl/StringData.h'
--- src/acl/StringData.h	2012-09-01 14:38:36 +0000
+++ src/acl/StringData.h	2012-11-13 18:18:05 +0000
@@ -35,29 +35,31 @@
 #define SQUID_ACLSTRINGDATA_H
 #include "splay.h"
 #include "acl/Acl.h"
 #include "acl/Data.h"
 
 class ACLStringData : public ACLData<char const *>
 {
 
 public:
     MEMPROXY_CLASS(ACLStringData);
 
     ACLStringData();
     ACLStringData(ACLStringData const &);
     ACLStringData &operator= (ACLStringData const &);
     virtual ~ACLStringData();
     bool match(char const *);
     wordlist *dump();
     void parse();
     bool empty() const;
     virtual ACLData<char const *> *clone() const;
+    /// Insert custom values
+    void insert(const char *);
 
     SplayNode<char *> *values;
 };
 
 /* TODO move into .cci files */
 
 MEMPROXY_CLASS_INLINE(ACLStringData);
 
 #endif /* SQUID_ACLSTRINGDATA_H */

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2012-11-13 18:13:50 +0000
+++ src/cf.data.pre	2012-11-13 18:18:05 +0000
@@ -895,40 +895,49 @@
 	acl aclname ssl_error errorname
 	  # match against SSL certificate validation error [fast]
 	  #
 	  # For valid error names see in @DEFAULT_ERROR_DIR@/templates/error-details.txt
 	  # template file.
 	  #
 	  # The following can be used as shortcuts for certificate properties:
 	  #  [ssl::]certHasExpired: the "not after" field is in the past
 	  #  [ssl::]certNotYetValid: the "not before" field is in the future
 	  #  [ssl::]certUntrusted: The certificate issuer is not to be trusted.
 	  #  [ssl::]certSelfSigned: The certificate is self signed.
 	  #  [ssl::]certDomainMismatch: The certificate CN domain does not
 	  #         match the name the name of the host we are connecting to.
 	  #
 	  # The ssl::certHasExpired, ssl::certNotYetValid, ssl::certDomainMismatch,
 	  # ssl::certUntrusted, and ssl::certSelfSigned can also be used as
 	  # predefined ACLs, just like the 'all' ACL.
 	  #
 	  # NOTE: The ssl_error ACL is only supported with sslproxy_cert_error,
 	  # sslproxy_cert_sign, and sslproxy_cert_adapt options.
+
+	acl aclname server_ssl_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).
 ENDIF
 
 	Examples:
 		acl macaddress arp 09:00:2b:23:45:67
 		acl myexample dst_as 1241
 		acl password proxy_auth REQUIRED
 		acl fileupload req_mime_type -i ^multipart/form-data$
 		acl javascript rep_mime_type -i ^application/x-javascript$
 
 NOCOMMENT_START
 #
 # Recommended minimum configuration:
 #
 
 # Example rule allowing access from your local networks.
 # Adapt to list your (internal) IP networks from where browsing
 # should be allowed
 acl localnet src 10.0.0.0/8	# RFC1918 possible internal network
 acl localnet src 172.16.0.0/12	# RFC1918 possible internal network
 acl localnet src 192.168.0.0/16	# RFC1918 possible internal network

=== modified file 'src/ssl/support.cc'
--- src/ssl/support.cc	2012-11-13 18:13:50 +0000
+++ src/ssl/support.cc	2012-11-13 18:18:05 +0000
@@ -263,48 +263,50 @@
         Ssl::Errors *errs = static_cast<Ssl::Errors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors));
         if (!errs) {
             errs = new Ssl::Errors(error_no);
             if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_errors,  (void *)errs)) {
                 debugs(83, 2, "Failed to set ssl error_no in ssl_verify_cb: Certificate " << buffer);
                 delete errs;
                 errs = NULL;
             }
         } else // remember another error number
             errs->push_back_unique(error_no);
 
         if (const char *err_descr = Ssl::GetErrorDescr(error_no))
             debugs(83, 5, err_descr << ": " << buffer);
         else
             debugs(83, DBG_IMPORTANT, "SSL unknown certificate error " << error_no << " in " << buffer);
 
         if (check) {
             ACLFilledChecklist *filledCheck = Filled(check);
             assert(!filledCheck->sslErrors);
             filledCheck->sslErrors = new Ssl::Errors(error_no);
+            filledCheck->serverCert.resetAndLock(peer_cert);
             if (check->fastCheck() == ACCESS_ALLOWED) {
                 debugs(83, 3, "bypassing SSL error " << error_no << " in " << buffer);
                 ok = 1;
             } else {
                 debugs(83, 5, "confirming SSL error " << error_no);
             }
             delete filledCheck->sslErrors;
             filledCheck->sslErrors = NULL;
+            filledCheck->serverCert.reset(NULL);
         }
 #if 1 // USE_SSL_CERT_VALIDATOR
         // If the certificate validator is used then we need to allow all errors and 
         // pass them to certficate validator for more processing
         else if (Ssl::TheConfig.ssl_crt_validator)
             ok = 1;
 #endif
     }
 
     if (!dont_verify_domain && server) {}
 
     if (!ok && !SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) ) {
 
         // Find the broken certificate. It may be intermediate.
         X509 *broken_cert = peer_cert; // reasonable default if search fails
         // Our SQUID_X509_V_ERR_DOMAIN_MISMATCH implies peer_cert is at fault.
         if (error_no != SQUID_X509_V_ERR_DOMAIN_MISMATCH) {
             if (X509 *last_used_cert = X509_STORE_CTX_get_current_cert(ctx))
                 broken_cert = last_used_cert;
         }
@@ -1156,86 +1158,120 @@
     if (strcmp(attribute_name, "DN") == 0) {
         X509_NAME_oneline(name, buffer, sizeof(buffer));
         goto done;
     }
 
     nid = OBJ_txt2nid((char *) attribute_name);
 
     if (nid == 0) {
         debugs(83, DBG_IMPORTANT, "WARNING: Unknown SSL attribute name '" << attribute_name << "'");
         return NULL;
     }
 
     X509_NAME_get_text_by_NID(name, nid, buffer, sizeof(buffer));
 
 done:
     return *buffer ? buffer : NULL;
 }
 
 /// \ingroup ServerProtocolSSLInternal
 const char *
-sslGetUserAttribute(SSL * ssl, const char *attribute_name)
+Ssl::GetX509UserAttribute(X509 * cert, const char *attribute_name)
 {
-    X509 *cert;
     X509_NAME *name;
     const char *ret;
 
-    if (!ssl)
-        return NULL;
-
-    cert = SSL_get_peer_certificate(ssl);
-
     if (!cert)
         return NULL;
 
     name = X509_get_subject_name(cert);
 
     ret = ssl_get_attribute(name, attribute_name);
 
-    X509_free(cert);
-
     return ret;
 }
 
+const char *
+Ssl::GetX509Fingerprint(X509 * cert, const char *)
+{
+    static char buf[1024];
+    if (!cert)
+        return NULL;
+    
+    unsigned int n;
+    unsigned char md[EVP_MAX_MD_SIZE];
+    if (!X509_digest(cert, EVP_sha1(), md, &n))
+        return NULL;
+
+    assert(3 * n + 1 < sizeof(buf));
+
+    char *s = buf;
+    for (unsigned int i=0; i < n; ++i, s += 3) {
+        const char term = (i + 1 < n) ? ':' : '\0';
+        snprintf(s, 4, "%02X%c", md[i], term);
+    }
+
+    return buf;
+}
+
 /// \ingroup ServerProtocolSSLInternal
 const char *
-sslGetCAAttribute(SSL * ssl, const char *attribute_name)
+Ssl::GetX509CAAttribute(X509 * cert, const char *attribute_name)
 {
-    X509 *cert;
+
     X509_NAME *name;
     const char *ret;
 
-    if (!ssl)
-        return NULL;
-
-    cert = SSL_get_peer_certificate(ssl);
-
     if (!cert)
         return NULL;
 
     name = X509_get_issuer_name(cert);
 
     ret = ssl_get_attribute(name, attribute_name);
 
+    return ret;
+}
+
+const char *sslGetUserAttribute(SSL *ssl, const char *attribute_name)
+{
+    if (!ssl)
+        return NULL;
+
+    X509 *cert = SSL_get_peer_certificate(ssl);
+
+    const char *attr = Ssl::GetX509UserAttribute(cert, attribute_name);
+
     X509_free(cert);
+    return attr;
+}
 
-    return ret;
+const char *sslGetCAAttribute(SSL *ssl, const char *attribute_name)
+{
+    if (!ssl)
+        return NULL;
+
+    X509 *cert = SSL_get_peer_certificate(ssl);
+
+    const char *attr = Ssl::GetX509CAAttribute(cert, attribute_name);
+
+    X509_free(cert);
+    return attr;
 }
 
 const char *
 sslGetUserEmail(SSL * ssl)
 {
     return sslGetUserAttribute(ssl, "emailAddress");
 }
 
 const char *
 sslGetUserCertificatePEM(SSL *ssl)
 {
     X509 *cert;
     BIO *mem;
     static char *str = NULL;
     char *ptr;
     long len;
 
     safe_free(str);
 
     if (!ssl)

=== modified file 'src/ssl/support.h'
--- src/ssl/support.h	2012-10-08 05:21:11 +0000
+++ src/ssl/support.h	2012-11-13 18:18:05 +0000
@@ -78,56 +78,65 @@
 
 /// \ingroup ServerProtocolSSLAPI
 SSL_CTX *sslCreateServerContext(AnyP::PortCfg &port);
 
 /// \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);
 
 /// \ingroup ServerProtocolSSLAPI
 void ssl_shutdown_method(SSL *ssl);
 
 /// \ingroup ServerProtocolSSLAPI
 const char *sslGetUserEmail(SSL *ssl);
 
 /// \ingroup ServerProtocolSSLAPI
-typedef char const *SSLGETATTRIBUTE(SSL *, const char *);
+const char *sslGetUserAttribute(SSL *ssl, const char *attribute_name);
 
 /// \ingroup ServerProtocolSSLAPI
-SSLGETATTRIBUTE sslGetUserAttribute;
-
-/// \ingroup ServerProtocolSSLAPI
-SSLGETATTRIBUTE sslGetCAAttribute;
+const char *sslGetCAAttribute(SSL *ssl, const char *attribute_name);
 
 /// \ingroup ServerProtocolSSLAPI
 const char *sslGetUserCertificatePEM(SSL *ssl);
 
 /// \ingroup ServerProtocolSSLAPI
 const char *sslGetUserCertificateChainPEM(SSL *ssl);
 
 namespace Ssl
 {
+/// \ingroup ServerProtocolSSLAPI
+typedef char const *GETX509ATTRIBUTE(X509 *, const char *);
+
+/// \ingroup ServerProtocolSSLAPI
+GETX509ATTRIBUTE GetX509UserAttribute;
+
+/// \ingroup ServerProtocolSSLAPI
+GETX509ATTRIBUTE GetX509CAAttribute;
+
+/// \ingroup ServerProtocolSSLAPI
+GETX509ATTRIBUTE GetX509Fingerprint;
+
 /**
   \ingroup ServerProtocolSSLAPI
  * Supported ssl-bump modes
  */
 enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpEnd};
 
 /**
  \ingroup  ServerProtocolSSLAPI
  * Short names for ssl-bump modes
  */
 extern const char *BumpModeStr[];
 
 /**
  \ingroup ServerProtocolSSLAPI
  * Return the short name of the ssl-bump mode "bm"
  */
 inline const char *bumpMode(int bm)
 {
     return (0 <= bm && bm < Ssl::bumpEnd) ? Ssl::BumpModeStr[bm] : NULL;
 }

=== modified file 'src/tests/stub_libsslsquid.cc'
--- src/tests/stub_libsslsquid.cc	2012-09-20 16:26:47 +0000
+++ src/tests/stub_libsslsquid.cc	2012-11-13 17:28:30 +0000
@@ -31,31 +31,31 @@
 void Ssl::GlobalContextStorage::addLocalStorage(Ip::Address const & address, size_t size_of_store) STUB
 Ssl::LocalContextStorage & Ssl::GlobalContextStorage::getLocalStorage(Ip::Address const & address)
 { fatal(STUB_API " required"); static Ssl::LocalContextStorage v(0); return v; }
 void Ssl::GlobalContextStorage::reconfigureStart() STUB
 //Ssl::GlobalContextStorage Ssl::TheGlobalContextStorage;
 
 #include "ssl/ErrorDetail.h"
 Ssl::ssl_error_t parseErrorString(const char *name) STUB_RETVAL(0)
 //const char *Ssl::getErrorName(ssl_error_t value) STUB_RETVAL(NULL)
 Ssl::ErrorDetail::ErrorDetail(ssl_error_t err_no, X509 *, X509 *, const char *) STUB
 Ssl::ErrorDetail::ErrorDetail(ErrorDetail const &) STUB
 const String & Ssl::ErrorDetail::toString() const STUB_RETSTATREF(String)
 
 #include "ssl/support.h"
 SSL_CTX *sslCreateServerContext(AnyP::PortCfg &) STUB_RETVAL(NULL)
 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) STUB_RETVAL(NULL)
 int ssl_read_method(int, char *, int) STUB_RETVAL(0)
 int ssl_write_method(int, const char *, int) STUB_RETVAL(0)
 void ssl_shutdown_method(SSL *) STUB
 const char *sslGetUserEmail(SSL *ssl) STUB_RETVAL(NULL)
-// typedef char const *SSLGETATTRIBUTE(SSL *, const char *);
-// SSLGETATTRIBUTE sslGetUserAttribute;
-// SSLGETATTRIBUTE sslGetCAAttribute;
+// typedef char const *Ssl::GETATTRIBUTE(X509 *, const char *);
+// Ssl::GETATTRIBUTE Ssl::GetX509UserAttribute;
+// Ssl::GETATTRIBUTE Ssl::GetX509CAAttribute;
 const char *sslGetUserCertificatePEM(SSL *ssl) STUB_RETVAL(NULL)
 const char *sslGetUserCertificateChainPEM(SSL *ssl) STUB_RETVAL(NULL)
 SSL_CTX * Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &) STUB_RETVAL(NULL)
 SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &) STUB_RETVAL(NULL)
 int Ssl::matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data,  ASN1_STRING *cn_data)) STUB_RETVAL(0)
 int Ssl::asn1timeToString(ASN1_TIME *tm, char *buf, int len) STUB_RETVAL(0)
 
 #endif

Reply via email to