]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Avoid reading tls-cert=bundle twice when loading certificates (#1073)
authorAlex Rousskov <rousskov@measurement-factory.com>
Tue, 14 Jun 2022 15:24:28 +0000 (15:24 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Thu, 16 Jun 2022 16:38:47 +0000 (16:38 +0000)
After opening and reading the certificate bundle to load the required
traffic-signing certificate, Squid was opening and reading the same file
again to load the optional intermediate certificates. Loading the
traffic-signing certificate twice triggered bugs and further
complicating naturally tricky code.

No functionality changes expected except minor cache.log message changes
and certificate reporting improvements in "squid -k parse" mode.

src/security/KeyData.cc
src/security/KeyData.h

index 73dc5ab23d7e3b07f497e0bbeb1b8004f28bb261..de083e90e630f8cb4652164537b55020fe9ebdb9 100644 (file)
 #include "ssl/bio.h"
 #include "ssl/gadgets.h"
 
-/// load a signing certificate from certFile
+/// load the signing certificate and its chain, if any, from certFile
+/// \return true if the signing certificate was obtained
 bool
-Security::KeyData::loadX509CertFromFile()
+Security::KeyData::loadCertificates()
 {
     debugs(83, DBG_IMPORTANT, "Using certificate in " << certFile);
     cert.reset(); // paranoid: ensure cert is unset
@@ -33,7 +34,7 @@ Security::KeyData::loadX509CertFromFile()
 
     try {
         cert = Ssl::ReadCertificate(bio);
-        return true;
+        debugs(83, DBG_PARSE_NOTE(2), "Loaded signing certificate: " << *cert);
     }
     catch (...) {
         // TODO: Convert the rest of this method to throw on errors instead.
@@ -42,6 +43,40 @@ Security::KeyData::loadX509CertFromFile()
         return false;
     }
 
+    try {
+        // selected bundled certificates in sending order: wireCerts = cert + chain
+        CertList wireCerts(1, cert);
+
+        while (const auto bundledCert = Ssl::ReadOptionalCertificate(bio)) {
+            assert(!wireCerts.empty()); // this->cert is there (at least)
+
+            // We cannot chain any certificate after a self-signed certificate. This
+            // check also protects the IssuedBy() check below from adding duplicated
+            // (i.e. listed multiple times in the bundle) self-signed certificates.
+            if (SelfSigned(*wireCerts.back())) {
+                debugs(83, DBG_PARSE_NOTE(2), "WARNING: Ignoring certificate after a self-signed one: " << *bundledCert);
+                continue; // ... but keep going to report all ignored certificates
+            }
+
+            // add the bundled certificate if it extends the chain further
+            if (IssuedBy(*wireCerts.back(), *bundledCert)) {
+                debugs(83, DBG_PARSE_NOTE(3), "Adding issuer CA: " << *bundledCert);
+                // OpenSSL API requires that we order certificates such that the
+                // chain can be appended directly into the on-wire traffic.
+                chain.emplace_back(bundledCert);
+                wireCerts.emplace_back(bundledCert);
+                continue;
+            }
+
+            debugs(83, DBG_PARSE_NOTE(2), "WARNING: Ignoring certificate that does not extend the chain: " << *bundledCert);
+        }
+    }
+    catch (...) {
+        // TODO: Reject configs with malformed intermediate certs instead.
+        debugs(83, DBG_IMPORTANT, "ERROR: Failure while loading intermediate certificate(s) from '" << certFile << "':" <<
+               Debug::Extra << "problem: " << CurrentException);
+    }
+
 #elif USE_GNUTLS
     const char *certFilename = certFile.c_str();
     gnutls_datum_t data;
@@ -73,6 +108,9 @@ Security::KeyData::loadX509CertFromFile()
         });
     }
 
+    // XXX: implement chain loading
+    debugs(83, 2, "Loading certificate chain from PEM files not implemented in this Squid.");
+
 #else
     // do nothing.
 #endif
@@ -84,58 +122,6 @@ Security::KeyData::loadX509CertFromFile()
     return bool(cert);
 }
 
-/// load any intermediate certs that form the chain with the loaded signing cert
-void
-Security::KeyData::loadX509ChainFromFile()
-{
-#if USE_OPENSSL
-    const char *certFilename = certFile.c_str();
-    Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
-    if (!bio || !BIO_read_filename(bio.get(), certFilename)) {
-        const auto x = ERR_get_error();
-        debugs(83, DBG_IMPORTANT, "ERROR: unable to load chain file '" << certFile << "': " << ErrorString(x));
-        return;
-    }
-
-    if (SelfSigned(*cert)) {
-        debugs(83, DBG_PARSE_NOTE(2), "Signing certificate is self-signed: " << *cert);
-        // TODO: Warn if there are other (unusable) certificates present.
-    } else {
-        debugs(83, DBG_PARSE_NOTE(3), "Using certificate chain in " << certFile);
-        // and add to the chain any other certificate exist in the file
-        CertPointer latestCert = cert;
-
-        while (const auto ca = Ssl::ReadOptionalCertificate(bio)) {
-            // checks that the chained certs are actually part of a chain for validating cert
-            if (IssuedBy(*latestCert, *ca)) {
-
-                if (SelfSigned(*latestCert)) { // TODO: Rename to lastChained
-                    Assure(SelfSigned(*ca));
-                    Assure(!SelfSigned(*cert)); // TODO: Rename to leafCert or signingCert
-                    debugs(83, DBG_PARSE_NOTE(2), "WARNING: Ignoring repeated Root CA: " << *ca);
-                    continue;
-                }
-
-                debugs(83, DBG_PARSE_NOTE(3), "Adding issuer CA: " << *ca);
-                // OpenSSL API requires that we order certificates such that the
-                // chain can be appended directly into the on-wire traffic.
-                latestCert = CertPointer(ca);
-                chain.emplace_back(latestCert);
-            } else {
-                debugs(83, DBG_PARSE_NOTE(2), certFile << ": Ignoring non-issuer CA " << *ca);
-            }
-        }
-    }
-
-#elif USE_GNUTLS
-    // XXX: implement chain loading
-    debugs(83, 2, "Loading certificate chain from PEM files not implemented in this Squid.");
-
-#else
-    // nothing to do.
-#endif
-}
-
 /**
  * Read X.509 private key from file.
  */
@@ -186,21 +172,11 @@ void
 Security::KeyData::loadFromFiles(const AnyP::PortCfg &port, const char *portType)
 {
     char buf[128];
-    if (!loadX509CertFromFile()) {
+    if (!loadCertificates()) {
         debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing certificate in '" << certFile << "'");
         return;
     }
 
-    // certificate chain in the PEM file is optional
-    try {
-        loadX509ChainFromFile();
-    }
-    catch (...) {
-        // XXX: Reject malformed configurations by letting exceptions propagate.
-        debugs(83, DBG_CRITICAL, "ERROR: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' cannot load intermediate certificates from '" << certFile << "':" <<
-               Debug::Extra << "problem: " << CurrentException);
-    }
-
     // pkey is mandatory, not having it makes cert and chain pointless.
     if (!loadX509PrivateKeyFromFile()) {
         debugs(83, DBG_IMPORTANT, "WARNING: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' missing private key in '" << privateKeyFile << "'");
index 97bbc72c2a06b56f7b5d6f298f1d6a682426a038..4f9b48903e44cf794a2633b36a59f63181f1ea18 100644 (file)
@@ -35,8 +35,7 @@ public:
     Security::CertList chain;
 
 private:
-    bool loadX509CertFromFile();
-    void loadX509ChainFromFile();
+    bool loadCertificates();
     bool loadX509PrivateKeyFromFile();
 };