]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Complete certificate chains using external intermediate certificates
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 18 Dec 2015 11:11:53 +0000 (00:11 +1300)
committerAmos Jeffries <squid3@treenet.co.nz>
Fri, 18 Dec 2015 11:11:53 +0000 (00:11 +1300)
 ... stored in sslproxy_foreign_intermediate_certs PEM file.

Many origin servers do not send complete certificate chains. Many
browsers use certificate extensions in the server certificate to
download the missing intermediate certificates automatically from
the Internet. Squid does not do that (yet?).

This patch adds the sslproxy_foreign_intermediate_certs configuration directive
to allow an admin to supply a file with intermediate certificates that
Squid may use to complete certificate chains. These intermediate
certificates are _not_ treated as trusted root certificates.

This is a Measurement Factory project.

doc/release-notes/release-3.5.sgml
src/SquidConfig.h
src/cache_cf.cc
src/cf.data.pre
src/ssl/support.cc
src/ssl/support.h

index 70dd948011b448bb29275988016ce1dd0967beba..7d7de051cf4e72da6b93237594c7da40799477fa 100644 (file)
@@ -346,6 +346,10 @@ This section gives a thorough account of those changes in three categories:
        <tag>sslproxy_cert_sign_hash</tag>
        <p>New directive to set the hashing algorithm to use when signing generated certificates.
 
+       <tag>sslproxy_foreign_intermediate_certs</tag>
+       <p>New directive to load intermediate certificates for validating server
+          certificate chains. This directive is only available in 3.5.13 and later.
+
        <tag>sslproxy_session_cache_size</tag>
        <p>New directive which sets the cache size to use for TLS/SSL sessions cache.
 
index 62e2b780316ebfa8ec8073f2e21a87717f216396..ebf39465e3b0a6b4dff8fee4dc6ce6bfe3046ac2 100644 (file)
@@ -18,6 +18,9 @@
 #include "icmp/IcmpConfig.h"
 #include "ip/Address.h"
 #include "Notes.h"
+#if USE_OPENSSL
+#include "ssl/support.h"
+#endif
 #include "YesNoNone.h"
 
 #if USE_OPENSSL
@@ -504,6 +507,7 @@ public:
         char *capath;
         char *crlfile;
         char *flags;
+        char *foreignIntermediateCertsPath;
         acl_access *cert_error;
         SSL_CTX *sslContext;
         sslproxy_cert_sign *cert_sign;
index ac51fb9b0a6b86603070e2821218d5ae9f8d1073..2e36f569233774c7d5e437ba361db8b7ebb8123b 100644 (file)
@@ -878,13 +878,18 @@ configDoConfigure(void)
     }
 
 #if USE_OPENSSL
+    if (Config.ssl_client.foreignIntermediateCertsPath)
+        Ssl::loadSquidUntrusted(Config.ssl_client.foreignIntermediateCertsPath);
+#endif
 
+#if USE_OPENSSL
     debugs(3, DBG_IMPORTANT, "Initializing https proxy context");
 
     Config.ssl_client.sslContext = sslCreateClientContext(Config.ssl_client.cert, Config.ssl_client.key, Config.ssl_client.version, Config.ssl_client.cipher, NULL, Config.ssl_client.flags, Config.ssl_client.cafile, Config.ssl_client.capath, Config.ssl_client.crlfile);
     // Pre-parse SSL client options to be applied when the client SSL objects created.
     // Options must not used in the case of peek or stare bump mode.
     Config.ssl_client.parsedOptions = Ssl::parse_options(::Config.ssl_client.options);
+    Ssl::useSquidUntrusted(Config.ssl_client.sslContext);
 
     for (CachePeer *p = Config.peers; p != NULL; p = p->next) {
         if (p->use_ssl) {
@@ -4003,6 +4008,7 @@ configFreeMemory(void)
     free_all();
 #if USE_OPENSSL
     SSL_CTX_free(Config.ssl_client.sslContext);
+    Ssl::unloadSquidUntrusted();
 #endif
 }
 
index 5827c4f4765aeb557c01d2f9bdc355654de9e054..a2e7a1eadc0012fc672c2042f65e9c2fb39ed317 100644 (file)
@@ -2581,6 +2581,26 @@ DOC_START
         Sets the cache size to use for ssl session
 DOC_END
 
+NAME: sslproxy_foreign_intermediate_certs
+IFDEF: USE_OPENSSL
+DEFAULT: none
+LOC: Config.ssl_client.foreignIntermediateCertsPath
+TYPE: string
+DOC_START
+       Many origin servers fail to send their full server certificate
+       chain for verification, assuming the client already has or can
+       easily locate any missing intermediate certificates.
+
+       Squid uses the certificates from the specified file to fill in
+       these missing chains when trying to validate origin server
+       certificate chains.
+
+       The file is expected to contain zero or more PEM-encoded
+       intermediate certificates. These certificates are not treated
+       as trusted root certificates, and any self-signed certificate in
+       this file will be ignored.
+DOC_END
+
 NAME: sslproxy_cert_sign_hash
 IFDEF: USE_OPENSSL
 DEFAULT: none
index 7ffa5fb822ed32905d0d583572cebf71a27ea386..d04090ca199d641378d8ab0196af6191c985bbf2 100644 (file)
@@ -36,6 +36,8 @@ static void setSessionCallbacks(SSL_CTX *ctx);
 Ipc::MemMap *SslSessionCache = NULL;
 const char *SslSessionCacheName = "ssl_session_cache";
 
+static Ssl::CertsIndexedList SquidUntrustedCerts;
+
 const EVP_MD *Ssl::DefaultSignHash = NULL;
 
 const char *Ssl::BumpModeStr[] = {
@@ -1762,6 +1764,134 @@ void Ssl::addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *chain)
     }
 }
 
+bool
+Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
+{
+    BIO *in = BIO_new_file(certsFile, "r");
+    if (!in) {
+        debugs(83, DBG_IMPORTANT, "Failed to open '" << certsFile << "' to load certificates");
+        return false;
+    }
+
+    X509 *aCert;
+    while((aCert = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
+        static char buffer[2048];
+        X509_NAME_oneline(X509_get_subject_name(aCert), buffer, sizeof(buffer));
+        list.insert(std::pair<SBuf, X509 *>(SBuf(buffer), aCert));
+    }
+    debugs(83, 4, "Loaded " << list.size() << " certificates from file: '" << certsFile << "'");
+    BIO_free(in);
+    return true;
+}
+
+/// quickly find a certificate with a given issuer in Ssl::CertsIndexedList.
+static X509 *
+findCertByIssuerFast(X509_STORE_CTX *ctx, Ssl::CertsIndexedList &list, X509 *cert)
+{
+    static char buffer[2048];
+
+    if (X509_NAME *issuerName = X509_get_issuer_name(cert))
+        X509_NAME_oneline(issuerName, buffer, sizeof(buffer));
+    else
+        return NULL;
+
+    const auto ret = list.equal_range(SBuf(buffer));
+    for (Ssl::CertsIndexedList::iterator it = ret.first; it != ret.second; ++it) {
+        X509 *issuer = it->second;
+        if (ctx->check_issued(ctx, cert, issuer)) {
+            return issuer;
+        }
+    }
+    return NULL;
+}
+
+/// slowly find a certificate with a given issuer using linear search
+static X509 *
+findCertByIssuerSlowly(X509_STORE_CTX *ctx, STACK_OF(X509) *sk, X509 *cert)
+{
+    const int skItemsNum = sk_X509_num(sk);
+    for (int i = 0; i < skItemsNum; ++i) {
+        X509 *issuer = sk_X509_value(sk, i);
+        if (ctx->check_issued(ctx, cert, issuer))
+            return issuer;
+    }
+    return NULL;
+}
+
+/// add missing issuer certificates to untrustedCerts
+static void
+completeIssuers(X509_STORE_CTX *ctx, STACK_OF(X509) *untrustedCerts)
+{
+    debugs(83, 2,  "completing " << sk_X509_num(untrustedCerts) << " OpenSSL untrusted certs using " << SquidUntrustedCerts.size() << " configured untrusted certificates");
+
+    int depth = ctx->param->depth;
+    X509 *current = ctx->cert;
+    int i = 0;
+    for (i = 0; current && (i < depth); ++i) {
+        if (ctx->check_issued(ctx, current, current)) {
+            // either ctx->cert is itself self-signed or untrustedCerts
+            // aready contain the self-signed current certificate
+            break;
+        }
+
+        // untrustedCerts is short, not worth indexing
+        X509 *issuer = findCertByIssuerSlowly(ctx, untrustedCerts, current);
+        if (!issuer) {
+            if ((issuer = findCertByIssuerFast(ctx, SquidUntrustedCerts, current)))
+                sk_X509_push(untrustedCerts, issuer);
+        }
+        current = issuer;
+    }
+
+    if (i >= depth)
+        debugs(83, 2,  "exceeded the maximum certificate chain length: " << depth);
+}
+
+/// OpenSSL certificate validation callback.
+static int
+untrustedToStoreCtx_cb(X509_STORE_CTX *ctx,void *data)
+{
+    debugs(83, 4,  "Try to use pre-downloaded intermediate certificates\n");
+
+    // OpenSSL already maintains ctx->untrusted but we cannot modify
+    // internal OpenSSL list directly. We have to give OpenSSL our own
+    // list, but it must include certificates on the OpenSSL ctx->untrusted
+    STACK_OF(X509) *oldUntrusted = ctx->untrusted;
+    STACK_OF(X509) *sk = sk_X509_dup(oldUntrusted); // oldUntrusted is always not NULL
+    completeIssuers(ctx, sk);
+    X509_STORE_CTX_set_chain(ctx, sk); // No locking/unlocking, just sets ctx->untrusted
+    int ret = X509_verify_cert(ctx);
+    X509_STORE_CTX_set_chain(ctx, oldUntrusted); // Set back the old untrusted list
+    sk_X509_free(sk); // Release sk list
+    return ret;
+}
+
+void
+Ssl::useSquidUntrusted(SSL_CTX *sslContext)
+{
+    if (SquidUntrustedCerts.size() > 0)
+        SSL_CTX_set_cert_verify_callback(sslContext, untrustedToStoreCtx_cb, NULL);
+    else
+        SSL_CTX_set_cert_verify_callback(sslContext, NULL, NULL);
+}
+
+bool
+Ssl::loadSquidUntrusted(const char *path)
+{
+    return Ssl::loadCerts(path, SquidUntrustedCerts);
+}
+
+void
+Ssl::unloadSquidUntrusted()
+{
+    if (SquidUntrustedCerts.size()) {
+        for (Ssl::CertsIndexedList::iterator it = SquidUntrustedCerts.begin(); it != SquidUntrustedCerts.end(); ++it) {
+            X509_free(it->second);
+        }
+        SquidUntrustedCerts.clear();
+    }
+}
+
 /**
  \ingroup ServerProtocolSSLInternal
  * Read certificate from file.
index d6fa9c48d27049177c1ec78d718dfa71d5adde1b..1d833cccecebd17de62b9ed3adfa62164fc4e020 100644 (file)
@@ -12,6 +12,7 @@
 #define SQUID_SSL_SUPPORT_H
 
 #include "CbDataList.h"
+#include "SBuf.h"
 #include "ssl/gadgets.h"
 
 #if HAVE_OPENSSL_SSL_H
@@ -26,6 +27,7 @@
 #if HAVE_OPENSSL_ENGINE_H
 #include <openssl/engine.h>
 #endif
+#include <map>
 
 /**
  \defgroup ServerProtocolSSLAPI Server-Side SSL API
@@ -193,6 +195,29 @@ ContextMethod contextMethod(int version);
 */
 bool generateUntrustedCert(X509_Pointer & untrustedCert, EVP_PKEY_Pointer & untrustedPkey, X509_Pointer const & cert, EVP_PKEY_Pointer const & pkey);
 
+/// certificates indexed by issuer name
+typedef std::multimap<SBuf, X509 *> CertsIndexedList;
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Load PEM-encoded certificates from the given file.
+ */
+bool loadCerts(const char *certsFile, Ssl::CertsIndexedList &list);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Load PEM-encoded certificates to the squid untrusteds certificates
+ * internal DB from the given file.
+ */
+bool loadSquidUntrusted(const char *path);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Removes all certificates from squid untrusteds certificates
+ * internal DB and frees all memory
+ */
+void unloadSquidUntrusted();
+
 /**
   \ingroup ServerProtocolSSLAPI
   * Decide on the kind of certificate and generate a CA- or self-signed one
@@ -241,6 +266,13 @@ bool configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::Po
  */
 void addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *certList);
 
+/**
+  \ingroup ServerProtocolSSLAPI
+  * Configures sslContext to use squid untrusted certificates internal list
+  * to complete certificate chains when verifies SSL servers certificates.
+ */
+void useSquidUntrusted(SSL_CTX *sslContext);
+
 /**
  \ingroup ServerProtocolSSLAPI
  *  Read certificate, private key and any certificates which must be chained from files.