From: Christos Tsantilas Date: Fri, 18 Dec 2015 11:11:53 +0000 (+1300) Subject: Complete certificate chains using external intermediate certificates X-Git-Tag: SQUID_3_5_13~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c398aa3f300160944d8681e674aa41ad65fd1e08;p=thirdparty%2Fsquid.git Complete certificate chains using external intermediate certificates ... 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. --- diff --git a/doc/release-notes/release-3.5.sgml b/doc/release-notes/release-3.5.sgml index 70dd948011..7d7de051cf 100644 --- a/doc/release-notes/release-3.5.sgml +++ b/doc/release-notes/release-3.5.sgml @@ -346,6 +346,10 @@ This section gives a thorough account of those changes in three categories: sslproxy_cert_sign_hash

New directive to set the hashing algorithm to use when signing generated certificates. + sslproxy_foreign_intermediate_certs +

New directive to load intermediate certificates for validating server + certificate chains. This directive is only available in 3.5.13 and later. + sslproxy_session_cache_size

New directive which sets the cache size to use for TLS/SSL sessions cache. diff --git a/src/SquidConfig.h b/src/SquidConfig.h index 62e2b78031..ebf39465e3 100644 --- a/src/SquidConfig.h +++ b/src/SquidConfig.h @@ -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; diff --git a/src/cache_cf.cc b/src/cache_cf.cc index ac51fb9b0a..2e36f56923 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -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 } diff --git a/src/cf.data.pre b/src/cf.data.pre index 5827c4f476..a2e7a1eadc 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -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 diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 7ffa5fb822..d04090ca19 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -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(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 d6fa9c48d2..1d833cccec 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -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 #endif +#include /** \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 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.