From 00352183dad4e7a947d1602ceecffbc7dd093244 Mon Sep 17 00:00:00 2001 From: Alex Rousskov Date: Fri, 14 Sep 2012 14:45:05 -0600 Subject: [PATCH] Initial server_ssl_cert_fingerprint ACL implementation. --- src/AclRegs.cc | 9 ++++- src/acl/Certificate.cc | 5 ++- src/acl/Certificate.h | 6 +-- src/acl/CertificateData.cc | 75 +++++++++++++++++++++++++++-------- src/acl/CertificateData.h | 22 +++++++--- src/acl/FilledChecklist.h | 2 + src/acl/Makefile.am | 2 + src/acl/StringData.cc | 6 +++ src/acl/StringData.h | 2 + src/cf.data.pre | 9 +++++ src/ssl/support.cc | 70 ++++++++++++++++++++++++-------- src/ssl/support.h | 19 ++++++--- src/tests/stub_libsslsquid.cc | 6 +-- 13 files changed, 181 insertions(+), 52 deletions(-) diff --git a/src/AclRegs.cc b/src/AclRegs.cc index 2035f9015a..db6e0bb2ed 100644 --- a/src/AclRegs.cc +++ b/src/AclRegs.cc @@ -59,6 +59,9 @@ #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" @@ -140,9 +143,11 @@ ACLStrategised ACLUrlPort::RegistryEntry_(new ACLIntRange, ACLUrlPortStrate ACL::Prototype ACLSslError::RegistryProtoype(&ACLSslError::RegistryEntry_, "ssl_error"); ACLStrategised ACLSslError::RegistryEntry_(new ACLSslErrorData, ACLSslErrorStrategy::Instance(), "ssl_error"); ACL::Prototype ACLCertificate::UserRegistryProtoype(&ACLCertificate::UserRegistryEntry_, "user_cert"); -ACLStrategised ACLCertificate::UserRegistryEntry_(new ACLCertificateData (sslGetUserAttribute), ACLCertificateStrategy::Instance(), "user_cert"); +ACLStrategised ACLCertificate::UserRegistryEntry_(new ACLCertificateData (Ssl::GetX509UserAttribute, "*"), ACLCertificateStrategy::Instance(), "user_cert"); ACL::Prototype ACLCertificate::CARegistryProtoype(&ACLCertificate::CARegistryEntry_, "ca_cert"); -ACLStrategised ACLCertificate::CARegistryEntry_(new ACLCertificateData (sslGetCAAttribute), ACLCertificateStrategy::Instance(), "ca_cert"); +ACLStrategised ACLCertificate::CARegistryEntry_(new ACLCertificateData (Ssl::GetX509CAAttribute, "*"), ACLCertificateStrategy::Instance(), "ca_cert"); +ACL::Prototype ACLServerCertificate::X509FingerprintRegistryProtoype(&ACLServerCertificate::X509FingerprintRegistryEntry_, "server_ssl_cert_fingerprint"); +ACLStrategised ACLServerCertificate::X509FingerprintRegistryEntry_(new ACLCertificateData(Ssl::GetX509Fingerprint, "-sha1", true), ACLServerCertificateStrategy::Instance(), "server_ssl_cert_fingerprint"); #endif #if USE_SQUID_EUI diff --git a/src/acl/Certificate.cc b/src/acl/Certificate.cc index 7b24581a07..e65ba407a8 100644 --- a/src/acl/Certificate.cc +++ b/src/acl/Certificate.cc @@ -55,7 +55,10 @@ ACLCertificateStrategy::match (ACLData * &data, ACLFilledChecklist *c 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 * diff --git a/src/acl/Certificate.h b/src/acl/Certificate.h index a2ccbbc3a5..d08f78abd4 100644 --- a/src/acl/Certificate.h +++ b/src/acl/Certificate.h @@ -42,7 +42,7 @@ #include "acl/Strategised.h" /// \ingroup ACLAPI -class ACLCertificateStrategy : public ACLStrategy +class ACLCertificateStrategy : public ACLStrategy { public: @@ -66,9 +66,9 @@ class ACLCertificate private: static ACL::Prototype UserRegistryProtoype; - static ACLStrategised UserRegistryEntry_; + static ACLStrategised UserRegistryEntry_; static ACL::Prototype CARegistryProtoype; - static ACLStrategised CARegistryEntry_; + static ACLStrategised CARegistryEntry_; }; #endif /* SQUID_ACLCERTIFICATE_H */ diff --git a/src/acl/CertificateData.cc b/src/acl/CertificateData.cc index 972e953616..4651e2480a 100644 --- a/src/acl/CertificateData.cc +++ b/src/acl/CertificateData.cc @@ -41,11 +41,25 @@ #include "protos.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); } @@ -70,13 +84,13 @@ splaystrcmp (T&l, T&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; @@ -94,7 +108,8 @@ 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. @@ -107,17 +122,45 @@ ACLCertificateData::dump() 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::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(); } @@ -128,7 +171,7 @@ ACLCertificateData::empty() const return values.empty(); } -ACLData * +ACLData * ACLCertificateData::clone() const { /* Splay trees don't clone yet. */ diff --git a/src/acl/CertificateData.h b/src/acl/CertificateData.h index 3ddbc855c8..5aec38f8ab 100644 --- a/src/acl/CertificateData.h +++ b/src/acl/CertificateData.h @@ -40,29 +40,41 @@ #include "acl/Data.h" #include "ssl/support.h" #include "acl/StringData.h" +#include +#include /// \ingroup ACLAPI -class ACLCertificateData : public ACLData +class ACLCertificateData : public ACLData { 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 *clone() const; + virtual ACLData *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 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); diff --git a/src/acl/FilledChecklist.h b/src/acl/FilledChecklist.h index 6c926d85ed..7ca55a8b2a 100644 --- a/src/acl/FilledChecklist.h +++ b/src/acl/FilledChecklist.h @@ -69,6 +69,8 @@ public: #if USE_SSL /// SSL [certificate validation] errors, in undefined order Ssl::Errors *sslErrors; + /// The peer certificate + Ssl::X509_Pointer serverCert; #endif ExternalACLEntry *extacl_entry; diff --git a/src/acl/Makefile.am b/src/acl/Makefile.am index 112230036b..d3c786ac79 100644 --- a/src/acl/Makefile.am +++ b/src/acl/Makefile.am @@ -118,6 +118,8 @@ SSL_ACLS = \ CertificateData.h \ Certificate.cc \ Certificate.h \ + ServerCertificate.cc \ + ServerCertificate.h \ SslError.cc \ SslError.h \ SslErrorData.cc \ diff --git a/src/acl/StringData.cc b/src/acl/StringData.cc index b257edad2c..fcdbf126bc 100644 --- a/src/acl/StringData.cc +++ b/src/acl/StringData.cc @@ -67,6 +67,12 @@ 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) { diff --git a/src/acl/StringData.h b/src/acl/StringData.h index aa4d593363..207528c140 100644 --- a/src/acl/StringData.h +++ b/src/acl/StringData.h @@ -54,6 +54,8 @@ public: void parse(); bool empty() const; virtual ACLData *clone() const; + /// Insert custom values + void insert(const char *); SplayNode *values; }; diff --git a/src/cf.data.pre b/src/cf.data.pre index 98fe7fd8e4..6433db3fab 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -910,6 +910,15 @@ IF USE_SSL # # 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: diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 1380531a6a..5da986022e 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -281,6 +281,7 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx) 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; @@ -289,6 +290,7 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx) } 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 @@ -1174,17 +1176,11 @@ done: /// \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; @@ -1192,24 +1188,40 @@ sslGetUserAttribute(SSL * ssl, const char *attribute_name) 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; @@ -1217,9 +1229,33 @@ sslGetCAAttribute(SSL * ssl, const char *attribute_name) 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 * diff --git a/src/ssl/support.h b/src/ssl/support.h index 4a49b9574f..62539f319f 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -97,13 +97,10 @@ void ssl_shutdown_method(SSL *ssl); 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); @@ -113,6 +110,18 @@ 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 diff --git a/src/tests/stub_libsslsquid.cc b/src/tests/stub_libsslsquid.cc index be72326115..20c3eb71ea 100644 --- a/src/tests/stub_libsslsquid.cc +++ b/src/tests/stub_libsslsquid.cc @@ -48,9 +48,9 @@ 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) -- 2.47.3