From: Christos Tsantilas Date: Wed, 2 Dec 2015 19:45:15 +0000 (+0200) Subject: Complete certificate chains using external intermediate certificates X-Git-Tag: SQUID_4_0_4~57 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=866be11cced134c6d9849462f2c12e387e141a9f;p=thirdparty%2Fsquid.git Complete certificate chains using external intermediate certificates stored in sslproxy_foreign_intermediate_certs 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. --- diff --git a/src/SquidConfig.h b/src/SquidConfig.h index 2e9d5abc53..d9398379e4 100644 --- a/src/SquidConfig.h +++ b/src/SquidConfig.h @@ -19,6 +19,9 @@ #include "Notes.h" #include "security/forward.h" #include "SquidTime.h" +#if USE_OPENSSL +#include "ssl/support.h" +#endif #include "store/forward.h" #include "YesNoNone.h" @@ -496,6 +499,7 @@ public: struct { Security::ContextPtr sslContext; #if USE_OPENSSL + char *foreignIntermediateCertsPath; acl_access *cert_error; sslproxy_cert_sign *cert_sign; sslproxy_cert_adapt *cert_adapt; diff --git a/src/cache_cf.cc b/src/cache_cf.cc index ae5d880e54..3c6c03d4eb 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -866,6 +866,11 @@ configDoConfigure(void) Config2.effectiveGroupID = grp->gr_gid; } +#if USE_OPENSSL + if (Config.ssl_client.foreignIntermediateCertsPath) + Ssl::loadSquidUntrusted(Config.ssl_client.foreignIntermediateCertsPath); +#endif + if (Security::ProxyOutgoingConfig.encryptTransport) { debugs(3, DBG_IMPORTANT, "Initializing https:// proxy context"); Config.ssl_client.sslContext = Security::ProxyOutgoingConfig.createClientContext(false); @@ -876,6 +881,9 @@ configDoConfigure(void) debugs(3, DBG_IMPORTANT, "ERROR: proxying https:// currently still requires --with-openssl"); #endif } +#if USE_OPENSSL + Ssl::useSquidUntrusted(Config.ssl_client.sslContext); +#endif } for (CachePeer *p = Config.peers; p != NULL; p = p->next) { @@ -3817,6 +3825,7 @@ configFreeMemory(void) free_all(); #if USE_OPENSSL SSL_CTX_free(Config.ssl_client.sslContext); + Ssl::unloadSquidUntrusted(); #endif } diff --git a/src/cf.data.pre b/src/cf.data.pre index 09a1f03b27..e8ef9ad3a3 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -2559,6 +2559,28 @@ 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. + + This directive may be repeated to load multiple files. +DOC_END + NAME: sslproxy_cert_sign_hash IFDEF: USE_OPENSSL DEFAULT: none diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 926ffd8a67..13e7e52c19 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -37,6 +37,8 @@ static void setSessionCallbacks(Security::ContextPtr 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[] = { @@ -1108,6 +1110,134 @@ void Ssl::addChainToSslContext(Security::ContextPtr sslContext, STACK_OF(X509) * } } +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(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. diff --git a/src/ssl/support.h b/src/ssl/support.h index 1e67895796..f4ebeb51a4 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -12,6 +12,7 @@ #define SQUID_SSL_SUPPORT_H #include "base/CbDataList.h" +#include "SBuf.h" #include "security/forward.h" #include "ssl/gadgets.h" @@ -24,6 +25,8 @@ #if HAVE_OPENSSL_ENGINE_H #include #endif +#include + /** \defgroup ServerProtocolSSLAPI Server-Side SSL API @@ -161,6 +164,29 @@ inline const char *bumpMode(int bm) */ bool generateUntrustedCert(Security::CertPointer & untrustedCert, EVP_PKEY_Pointer & untrustedPkey, Security::CertPointer const & cert, EVP_PKEY_Pointer const & pkey); +/// certificates indexed by issuer name +typedef std::multimap 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 @@ -209,6 +235,13 @@ bool configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::Po */ void addChainToSslContext(Security::ContextPtr 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.