]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ssl/support.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / ssl / support.cc
index 40b92caf8e09c92c08a98da8dc5d0c115d71f309..3ec51fdfad5476814b6ff1b4090aba53bc2119bd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
 
 #include "acl/FilledChecklist.h"
 #include "anyp/PortCfg.h"
+#include "anyp/Uri.h"
 #include "fatal.h"
 #include "fd.h"
 #include "fde.h"
 #include "globals.h"
 #include "ipc/MemMap.h"
 #include "security/CertError.h"
+#include "security/ErrorDetail.h"
 #include "security/Session.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 #include "ssl/ErrorDetail.h"
 #include "ssl/gadgets.h"
 #include "ssl/support.h"
-#include "URL.h"
 
 #include <cerrno>
 
 // TODO: Move ssl_ex_index_* global variables from global.cc here.
-int ssl_ex_index_ssl_untrusted_chain = -1;
-
-Ipc::MemMap *Ssl::SessionCache = NULL;
-const char *Ssl::SessionCacheName = "ssl_session_cache";
+static int ssl_ex_index_verify_callback_parameters = -1;
 
 static Ssl::CertsIndexedList SquidUntrustedCerts;
 
@@ -62,15 +60,14 @@ std::vector<const char *> Ssl::BumpModeStr = {
  \ingroup ServerProtocolSSLAPI
  */
 
-/// \ingroup ServerProtocolSSLInternal
-static int
-ssl_ask_password_cb(char *buf, int size, int rwflag, void *userdata)
+int
+Ssl::AskPasswordCb(char *buf, int size, int rwflag, void *userdata)
 {
     FILE *in;
     int len = 0;
     char cmdline[1024];
 
-    snprintf(cmdline, sizeof(cmdline), "\"%s\" \"%s\"", Config.Program.ssl_password, (const char *)userdata);
+    snprintf(cmdline, sizeof(cmdline), "\"%s\" \"%s\"", ::Config.Program.ssl_password, (const char *)userdata);
     in = popen(cmdline, "r");
 
     if (fgets(buf, size, in))
@@ -92,7 +89,7 @@ static void
 ssl_ask_password(SSL_CTX * context, const char * prompt)
 {
     if (Config.Program.ssl_password) {
-        SSL_CTX_set_default_passwd_cb(context, ssl_ask_password_cb);
+        SSL_CTX_set_default_passwd_cb(context, Ssl::AskPasswordCb);
         SSL_CTX_set_default_passwd_cb_userdata(context, (void *)prompt);
     }
 }
@@ -101,18 +98,34 @@ ssl_ask_password(SSL_CTX * context, const char * prompt)
 static RSA *
 ssl_temp_rsa_cb(SSL * ssl, int anInt, int keylen)
 {
-    static RSA *rsa_512 = NULL;
-    static RSA *rsa_1024 = NULL;
-    RSA *rsa = NULL;
+    static RSA *rsa_512 = nullptr;
+    static RSA *rsa_1024 = nullptr;
+    static BIGNUM *e = nullptr;
+    RSA *rsa = nullptr;
     int newkey = 0;
 
+    if (!e) {
+        e = BN_new();
+        if (!e || !BN_set_word(e, RSA_F4)) {
+            debugs(83, DBG_IMPORTANT, "ssl_temp_rsa_cb: Failed to set exponent for key " << keylen);
+            BN_free(e);
+            e = nullptr;
+            return nullptr;
+        }
+    }
+
     switch (keylen) {
 
     case 512:
 
         if (!rsa_512) {
-            rsa_512 = RSA_generate_key(512, RSA_F4, NULL, NULL);
-            newkey = 1;
+            rsa_512 = RSA_new();
+            if (rsa_512 && RSA_generate_key_ex(rsa_512, 512, e, nullptr)) {
+                newkey = 1;
+            } else {
+                RSA_free(rsa_512);
+                rsa_512 = nullptr;
+            }
         }
 
         rsa = rsa_512;
@@ -121,8 +134,13 @@ ssl_temp_rsa_cb(SSL * ssl, int anInt, int keylen)
     case 1024:
 
         if (!rsa_1024) {
-            rsa_1024 = RSA_generate_key(1024, RSA_F4, NULL, NULL);
-            newkey = 1;
+            rsa_1024 = RSA_new();
+            if (rsa_1024 && RSA_generate_key_ex(rsa_1024, 1024, e, nullptr)) {
+                newkey = 1;
+            } else {
+                RSA_free(rsa_1024);
+                rsa_1024 = nullptr;
+            }
         }
 
         rsa = rsa_1024;
@@ -149,8 +167,8 @@ ssl_temp_rsa_cb(SSL * ssl, int anInt, int keylen)
 }
 #endif
 
-static void
-maybeSetupRsaCallback(Security::ContextPointer &ctx)
+void
+Ssl::MaybeSetupRsaCallback(Security::ContextPointer &ctx)
 {
 #if HAVE_LIBSSL_SSL_CTX_SET_TMP_RSA_CALLBACK
     debugs(83, 9, "Setting RSA key generation callback.");
@@ -236,14 +254,8 @@ bool Ssl::checkX509ServerValidity(X509 *cert, const char *server)
     return matchX509CommonNames(cert, (void *)server, check_domain);
 }
 
-#if !HAVE_LIBCRYPTO_X509_STORE_CTX_GET0_CERT
-static inline X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *ctx)
-{
-    return ctx->cert;
-}
-#endif
-
-/// \ingroup ServerProtocolSSLInternal
+/// adjusts OpenSSL validation results for each verified certificate in ctx
+/// OpenSSL "verify_callback function" (\ref OpenSSL_vcb_disambiguation)
 static int
 ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
 {
@@ -301,6 +313,16 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
         }
     }
 
+    if (!ok && error_no == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) {
+        if (const auto params = Ssl::VerifyCallbackParameters::Find(*ssl)) {
+            if (params->callerHandlesMissingCertificates) {
+                debugs(83, 3, "hiding X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
+                params->hidMissingIssuer = true;
+                ok = 1;
+            }
+        }
+    }
+
     if (!ok) {
         Security::CertPointer broken_cert;
         broken_cert.resetAndLock(X509_STORE_CTX_get_current_cert(ctx));
@@ -332,7 +354,7 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
                 assert(!filledCheck->sslErrors);
                 filledCheck->sslErrors = new Security::CertErrors(Security::CertError(error_no, broken_cert));
                 filledCheck->serverCert = peer_cert;
-                if (check->fastCheck() == ACCESS_ALLOWED) {
+                if (check->fastCheck().allowed()) {
                     debugs(83, 3, "bypassing SSL error " << error_no << " in " << buffer);
                     ok = 1;
                 } else {
@@ -343,7 +365,7 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
                 filledCheck->serverCert.reset();
             }
             // If the certificate validator is used then we need to allow all errors and
-            // pass them to certficate validator for more processing
+            // pass them to certificate validator for more processing
             else if (Ssl::TheConfig.ssl_crt_validator) {
                 ok = 1;
             }
@@ -369,16 +391,171 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
                 broken_cert.resetAndLock(last_used_cert);
         }
 
-        auto *errDetail = new Ssl::ErrorDetail(error_no, peer_cert.get(), broken_cert.get());
-        if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, errDetail)) {
-            debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer);
-            delete errDetail;
-        }
+        std::unique_ptr<Security::ErrorDetail::Pointer> edp(new Security::ErrorDetail::Pointer(
+                    new Security::ErrorDetail(error_no, peer_cert, broken_cert)));
+        if (SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, edp.get()))
+            edp.release();
+        else
+            debugs(83, 2, "failed to store a " << buffer << " error detail: " << *edp);
     }
 
     return ok;
 }
 
+void
+Ssl::ConfigurePeerVerification(Security::ContextPointer &ctx, const Security::ParsedPortFlags flags)
+{
+    int mode;
+
+    // assume each flag is exclusive; flags creator must check this assumption
+    if (flags & SSL_FLAG_DONT_VERIFY_PEER) {
+        debugs(83, DBG_IMPORTANT, "SECURITY WARNING: Peer certificates are not verified for validity!");
+        debugs(83, DBG_IMPORTANT, "UPGRADE NOTICE: The DONT_VERIFY_PEER flag is deprecated. Remove the clientca= option to disable client certificates.");
+        mode = SSL_VERIFY_NONE;
+    }
+    else if (flags & SSL_FLAG_DELAYED_AUTH) {
+        debugs(83, DBG_PARSE_NOTE(3), "not requesting client certificates until ACL processing requires one");
+        mode = SSL_VERIFY_NONE;
+    }
+    else if (flags & SSL_FLAG_CONDITIONAL_AUTH) {
+        debugs(83, DBG_PARSE_NOTE(3), "will request the client certificate but ignore its absence");
+        mode = SSL_VERIFY_PEER;
+    }
+    else {
+        debugs(83, DBG_PARSE_NOTE(3), "Requiring client certificates.");
+        mode = SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
+    }
+
+    SSL_CTX_set_verify(ctx.get(), mode, (mode != SSL_VERIFY_NONE) ? ssl_verify_cb : nullptr);
+}
+
+void
+Ssl::DisablePeerVerification(Security::ContextPointer &ctx)
+{
+    debugs(83, DBG_PARSE_NOTE(3), "Not requiring any client certificates");
+    SSL_CTX_set_verify(ctx.get(),SSL_VERIFY_NONE,nullptr);
+}
+
+static int VerifyCtxCertificates(X509_STORE_CTX *ctx, STACK_OF(X509) *extraCerts);
+
+bool
+Ssl::VerifyConnCertificates(Security::Connection &sconn, const Ssl::X509_STACK_Pointer &extraCerts)
+{
+    const auto peerCertificatesChain = SSL_get_peer_cert_chain(&sconn);
+
+    // TODO: Replace debugs/return false with returning ErrorDetail::Pointer.
+    // Using Security::ErrorDetail terminology, errors in _this_ function are
+    // "non-validation errors", but VerifyCtxCertificates() errors may be
+    // "certificate validation errors". Callers detail SQUID_TLS_ERR_CONNECT.
+    // Some details should be created right here. Others extracted from OpenSSL.
+    // Why not throw? Most of the reasons detailed in the following commit apply
+    // here as well: https://github.com/measurement-factory/squid/commit/e862d33
+
+    if (!peerCertificatesChain || sk_X509_num(peerCertificatesChain) == 0) {
+        debugs(83, 2, "no server certificates");
+        return false;
+    }
+
+    const auto verificationStore = SSL_CTX_get_cert_store(SSL_get_SSL_CTX(&sconn));
+    if (!verificationStore) {
+        debugs(83, 2, "no certificate store");
+        return false;
+    }
+
+    const X509_STORE_CTX_Pointer storeCtx(X509_STORE_CTX_new());
+    if (!storeCtx) {
+        debugs(83, 2, "cannot create X509_STORE_CTX; likely OOM");
+        return false;
+    }
+
+    const auto peerCert = sk_X509_value(peerCertificatesChain, 0);
+    if (!X509_STORE_CTX_init(storeCtx.get(), verificationStore, peerCert, peerCertificatesChain)) {
+        debugs(83, 2, "cannot initialize X509_STORE_CTX");
+        return false;
+    }
+
+#if defined(SSL_CERT_FLAG_SUITEB_128_LOS)
+    // overwrite context Suite B (RFC 5759) flags with connection non-defaults
+    // SSL_set_cert_flags() return type is long, but its implementation actually
+    // returns an unsigned long flags value expected by X509_STORE_CTX_set_flags
+    const unsigned long certFlags = SSL_set_cert_flags(&sconn, 0);
+    if (const auto suiteBflags = certFlags & SSL_CERT_FLAG_SUITEB_128_LOS)
+        X509_STORE_CTX_set_flags(storeCtx.get(), suiteBflags);
+#endif
+
+    if (!X509_STORE_CTX_set_ex_data(storeCtx.get(), SSL_get_ex_data_X509_STORE_CTX_idx(), &sconn)) {
+        debugs(83, 2, "cannot attach SSL object to X509_STORE_CTX");
+        return false;
+    }
+
+    // If we ever add DANE support to Squid, we will supply DANE details here:
+    // X509_STORE_CTX_set0_dane(storeCtx.get(), SSL_get0_dane(&sconn));
+
+    // tell OpenSSL we are verifying a server certificate
+    if (!X509_STORE_CTX_set_default(storeCtx.get(), "ssl_server")) {
+        debugs(83, 2, "cannot set default verification method to ssl_server");
+        return false;
+    }
+
+    // overwrite context "verification parameters" with connection non-defaults
+    const auto param = X509_STORE_CTX_get0_param(storeCtx.get());
+    if (!param) {
+        debugs(83, 2, "no context verification parameters");
+        return false;
+    }
+#if defined(HAVE_X509_VERIFY_PARAM_SET_AUTH_LEVEL)
+    X509_VERIFY_PARAM_set_auth_level(param, SSL_get_security_level(&sconn));
+#endif
+    if (!X509_VERIFY_PARAM_set1(param, SSL_get0_param(&sconn))) {
+        debugs(83, 2, "cannot overwrite context verification parameters");
+        return false;
+    }
+
+    // copy any connection "verify_callback function" to the validation context
+    // (\ref OpenSSL_vcb_disambiguation)
+    if (const auto cb = SSL_get_verify_callback(&sconn))
+        X509_STORE_CTX_set_verify_cb(storeCtx.get(), cb);
+
+    // verify the server certificate chain in the prepared validation context
+    if (VerifyCtxCertificates(storeCtx.get(), extraCerts.get()) <= 0) {
+        // see also: ssl_verify_cb() details errors via ssl_ex_index_ssl_errors
+        const auto verifyResult = X509_STORE_CTX_get_error(storeCtx.get());
+        debugs(83, 3, "verification failure: " << verifyResult << ' ' << X509_verify_cert_error_string(verifyResult));
+        return false;
+    }
+
+    debugs(83, 7, "success");
+    return true;
+}
+
+/* Ssl::VerifyCallbackParameters */
+
+Ssl::VerifyCallbackParameters *
+Ssl::VerifyCallbackParameters::Find(Security::Connection &sconn)
+{
+    return static_cast<VerifyCallbackParameters*>(SSL_get_ex_data(&sconn, ssl_ex_index_verify_callback_parameters));
+}
+
+Ssl::VerifyCallbackParameters *
+Ssl::VerifyCallbackParameters::New(Security::Connection &sconn)
+{
+    Must(!Find(sconn));
+    const auto parameters = new VerifyCallbackParameters();
+    if (!SSL_set_ex_data(&sconn, ssl_ex_index_verify_callback_parameters, parameters)) {
+        delete parameters;
+        throw TextException("SSL_set_ex_data() failed; likely OOM", Here());
+    }
+    return parameters;
+}
+
+Ssl::VerifyCallbackParameters &
+Ssl::VerifyCallbackParameters::At(Security::Connection &sconn)
+{
+    const auto parameters = Find(sconn);
+    Must(parameters);
+    return *parameters;
+}
+
 // "dup" function for SSL_get_ex_new_index("cert_err_check")
 #if SQUID_USE_CONST_CRYPTO_EX_DATA_DUP
 static int
@@ -409,7 +586,7 @@ static void
 ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *,
                      int, long, void *)
 {
-    Ssl::ErrorDetail  *errDetail = static_cast <Ssl::ErrorDetail *>(ptr);
+    const auto errDetail = static_cast<Security::ErrorDetail::Pointer*>(ptr);
     delete errDetail;
 }
 
@@ -459,6 +636,14 @@ ssl_free_SBuf(void *, void *ptr, CRYPTO_EX_DATA *,
     delete buf;
 }
 
+/// "free" function for the ssl_ex_index_verify_callback_parameters entry
+static void
+ssl_free_VerifyCallbackParameters(void *, void *ptr, CRYPTO_EX_DATA *,
+                                  int, long, void *)
+{
+    delete static_cast<Ssl::VerifyCallbackParameters*>(ptr);
+}
+
 void
 Ssl::Initialize(void)
 {
@@ -467,17 +652,17 @@ Ssl::Initialize(void)
         return;
     initialized = true;
 
-    SSL_load_error_strings();
-    SSLeay_add_ssl_algorithms();
+    SQUID_OPENSSL_init_ssl();
 
-#if HAVE_OPENSSL_ENGINE_H
+#if !defined(OPENSSL_NO_ENGINE)
     if (::Config.SSL.ssl_engine) {
+        ENGINE_load_builtin_engines();
         ENGINE *e;
         if (!(e = ENGINE_by_id(::Config.SSL.ssl_engine)))
             fatalf("Unable to find SSL engine '%s'\n", ::Config.SSL.ssl_engine);
 
         if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
-            const int ssl_error = ERR_get_error();
+            const auto ssl_error = ERR_get_error();
             fatalf("Failed to initialise SSL engine: %s\n", Security::ErrorString(ssl_error));
         }
     }
@@ -499,74 +684,7 @@ Ssl::Initialize(void)
     ssl_ex_index_ssl_errors =  SSL_get_ex_new_index(0, (void *) "ssl_errors", NULL, NULL, &ssl_free_SslErrors);
     ssl_ex_index_ssl_cert_chain = SSL_get_ex_new_index(0, (void *) "ssl_cert_chain", NULL, NULL, &ssl_free_CertChain);
     ssl_ex_index_ssl_validation_counter = SSL_get_ex_new_index(0, (void *) "ssl_validation_counter", NULL, NULL, &ssl_free_int);
-    ssl_ex_index_ssl_untrusted_chain = SSL_get_ex_new_index(0, (void *) "ssl_untrusted_chain", NULL, NULL, &ssl_free_CertChain);
-}
-
-static bool
-configureSslContext(Security::ContextPointer &ctx, AnyP::PortCfg &port)
-{
-    int ssl_error;
-    SSL_CTX_set_options(ctx.get(), port.secure.parsedOptions);
-
-    if (port.sslContextSessionId)
-        SSL_CTX_set_session_id_context(ctx.get(), (const unsigned char *)port.sslContextSessionId, strlen(port.sslContextSessionId));
-
-    if (port.secure.parsedFlags & SSL_FLAG_NO_SESSION_REUSE) {
-        SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_OFF);
-    }
-
-    if (Config.SSL.unclean_shutdown) {
-        debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation).");
-
-        SSL_CTX_set_quiet_shutdown(ctx.get(), 1);
-    }
-
-    if (!port.secure.sslCipher.isEmpty()) {
-        debugs(83, 5, "Using chiper suite " << port.secure.sslCipher << ".");
-
-        if (!SSL_CTX_set_cipher_list(ctx.get(), port.secure.sslCipher.c_str())) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to set SSL cipher suite '" << port.secure.sslCipher << "': " << Security::ErrorString(ssl_error));
-            return false;
-        }
-    }
-
-    maybeSetupRsaCallback(ctx);
-
-    port.secure.updateContextEecdh(ctx);
-    port.secure.updateContextCa(ctx);
-
-    if (port.clientCA.get()) {
-        ERR_clear_error();
-        if (STACK_OF(X509_NAME) *clientca = SSL_dup_CA_list(port.clientCA.get())) {
-            SSL_CTX_set_client_CA_list(ctx.get(), clientca);
-        } else {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to dupe the client CA list: " << Security::ErrorString(ssl_error));
-            return false;
-        }
-
-        if (port.secure.parsedFlags & SSL_FLAG_DELAYED_AUTH) {
-            debugs(83, 9, "Not requesting client certificates until acl processing requires one");
-            SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, NULL);
-        } else {
-            debugs(83, 9, "Requiring client certificates.");
-            SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb);
-        }
-
-        port.secure.updateContextCrl(ctx);
-
-    } else {
-        debugs(83, 9, "Not requiring any client certificates");
-        SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, NULL);
-    }
-
-    if (port.secure.parsedFlags & SSL_FLAG_DONT_VERIFY_DOMAIN)
-        SSL_CTX_set_ex_data(ctx.get(), ssl_ctx_ex_index_dont_verify_domain, (void *) -1);
-
-    Ssl::SetSessionCallbacks(ctx);
-
-    return true;
+    ssl_ex_index_verify_callback_parameters = SSL_get_ex_new_index(0, (void *) "verify_callback_parameters", nullptr, nullptr, &ssl_free_VerifyCallbackParameters);
 }
 
 bool
@@ -575,60 +693,11 @@ Ssl::InitServerContext(Security::ContextPointer &ctx, AnyP::PortCfg &port)
     if (!ctx)
         return false;
 
-    if (!SSL_CTX_use_certificate(ctx.get(), port.signingCert.get())) {
-        const int ssl_error = ERR_get_error();
-        const auto &keys = port.secure.certs.front();
-        debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS certificate '" << keys.certFile << "': " << Security::ErrorString(ssl_error));
-        return false;
-    }
-
-    if (!SSL_CTX_use_PrivateKey(ctx.get(), port.signPkey.get())) {
-        const int ssl_error = ERR_get_error();
-        const auto &keys = port.secure.certs.front();
-        debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS private key '" << keys.privateKeyFile << "': " << Security::ErrorString(ssl_error));
-        return false;
-    }
-
-    Ssl::addChainToSslContext(ctx, port.certsToChain.get());
-
-    /* Alternate code;
-        debugs(83, DBG_IMPORTANT, "Using certificate in " << certfile);
-
-        if (!SSL_CTX_use_certificate_chain_file(ctx.get(), certfile)) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL certificate '" << certfile << "': " << Security::ErrorString(ssl_error));
-            return false;
-        }
-
-        debugs(83, DBG_IMPORTANT, "Using private key in " << keyfile);
-        ssl_ask_password(ctx.get(), keyfile);
-
-        if (!SSL_CTX_use_PrivateKey_file(ctx.get(), keyfile, SSL_FILETYPE_PEM)) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL private key '" << keyfile << "': " << Security::ErrorString(ssl_error));
-            return false;
-        }
-
-        debugs(83, 5, "Comparing private and public SSL keys.");
-
-        if (!SSL_CTX_check_private_key(ctx.get())) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: SSL private key '" << certfile << "' does not match public key '" <<
-                   keyfile << "': " << Security::ErrorString(ssl_error));
-            return false;
-        }
-    */
-
-    if (!configureSslContext(ctx, port)) {
-        debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context");
-        return false;
-    }
-
     return true;
 }
 
 bool
-Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, long fl)
+Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, Security::ParsedPortFlags fl)
 {
     if (!ctx)
         return false;
@@ -638,7 +707,7 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
 
         const char *cipher = peer.sslCipher.c_str();
         if (!SSL_CTX_set_cipher_list(ctx.get(), cipher)) {
-            const int ssl_error = ERR_get_error();
+            const auto ssl_error = ERR_get_error();
             fatalf("Failed to set SSL cipher suite '%s': %s\n",
                    cipher, Security::ErrorString(ssl_error));
         }
@@ -652,7 +721,7 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
 
             const char *certfile = keys.certFile.c_str();
             if (!SSL_CTX_use_certificate_chain_file(ctx.get(), certfile)) {
-                const int ssl_error = ERR_get_error();
+                const auto ssl_error = ERR_get_error();
                 fatalf("Failed to acquire SSL certificate '%s': %s\n",
                        certfile, Security::ErrorString(ssl_error));
             }
@@ -662,7 +731,7 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
             ssl_ask_password(ctx.get(), keyfile);
 
             if (!SSL_CTX_use_PrivateKey_file(ctx.get(), keyfile, SSL_FILETYPE_PEM)) {
-                const int ssl_error = ERR_get_error();
+                const auto ssl_error = ERR_get_error();
                 fatalf("Failed to acquire SSL private key '%s': %s\n",
                        keyfile, Security::ErrorString(ssl_error));
             }
@@ -670,22 +739,16 @@ Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &pee
             debugs(83, 5, "Comparing private and public SSL keys.");
 
             if (!SSL_CTX_check_private_key(ctx.get())) {
-                const int ssl_error = ERR_get_error();
+                const auto ssl_error = ERR_get_error();
                 fatalf("SSL private key '%s' does not match public key '%s': %s\n",
                        certfile, keyfile, Security::ErrorString(ssl_error));
             }
         }
     }
 
-    maybeSetupRsaCallback(ctx);
+    MaybeSetupRsaCallback(ctx);
 
-    if (fl & SSL_FLAG_DONT_VERIFY_PEER) {
-        debugs(83, 2, "NOTICE: Peer certificates are not verified for validity!");
-        SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, NULL);
-    } else {
-        debugs(83, 9, "Setting certificate verification callback.");
-        SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb);
-    }
+    Ssl::ConfigurePeerVerification(ctx, fl);
 
     return true;
 }
@@ -751,6 +814,19 @@ Ssl::GetX509Fingerprint(X509 * cert, const char *)
     return buf;
 }
 
+SBuf
+Ssl::GetX509PEM(X509 * cert)
+{
+    assert(cert);
+
+    Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
+    PEM_write_bio_X509(bio.get(), cert);
+
+    char *ptr;
+    const auto len = BIO_get_mem_data(bio.get(), &ptr);
+    return SBuf(ptr, len);
+}
+
 /// \ingroup ServerProtocolSSLInternal
 const char *
 Ssl::GetX509CAAttribute(X509 * cert, const char *attribute_name)
@@ -801,87 +877,44 @@ sslGetUserEmail(SSL * ssl)
     return sslGetUserAttribute(ssl, "emailAddress");
 }
 
-const char *
+SBuf
 sslGetUserCertificatePEM(SSL *ssl)
 {
-    X509 *cert;
-    BIO *mem;
-    static char *str = NULL;
-    char *ptr;
-    long len;
-
-    safe_free(str);
-
-    if (!ssl)
-        return NULL;
-
-    cert = SSL_get_peer_certificate(ssl);
-
-    if (!cert)
-        return NULL;
-
-    mem = BIO_new(BIO_s_mem());
-
-    PEM_write_bio_X509(mem, cert);
-
-    len = BIO_get_mem_data(mem, &ptr);
-
-    str = (char *)xmalloc(len + 1);
-
-    memcpy(str, ptr, len);
+    assert(ssl);
 
-    str[len] = '\0';
+    if (const auto cert = SSL_get_peer_certificate(ssl))
+        return Ssl::GetX509PEM(cert);
 
-    X509_free(cert);
-
-    BIO_free(mem);
-
-    return str;
+    return SBuf();
 }
 
-const char *
+SBuf
 sslGetUserCertificateChainPEM(SSL *ssl)
 {
-    STACK_OF(X509) *chain;
-    BIO *mem;
-    static char *str = NULL;
-    char *ptr;
-    long len;
-    int i;
+    assert(ssl);
 
-    safe_free(str);
-
-    if (!ssl)
-        return NULL;
-
-    chain = SSL_get_peer_cert_chain(ssl);
+    auto chain = SSL_get_peer_cert_chain(ssl);
 
     if (!chain)
         return sslGetUserCertificatePEM(ssl);
 
-    mem = BIO_new(BIO_s_mem());
+    Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
 
-    for (i = 0; i < sk_X509_num(chain); ++i) {
+    for (int i = 0; i < sk_X509_num(chain); ++i) {
         X509 *cert = sk_X509_value(chain, i);
-        PEM_write_bio_X509(mem, cert);
+        PEM_write_bio_X509(bio.get(), cert);
     }
 
-    len = BIO_get_mem_data(mem, &ptr);
-
-    str = (char *)xmalloc(len + 1);
-    memcpy(str, ptr, len);
-    str[len] = '\0';
-
-    BIO_free(mem);
-
-    return str;
+    char *ptr;
+    const auto len = BIO_get_mem_data(bio.get(), &ptr);
+    return SBuf(ptr, len);
 }
 
 /// Create SSL context and apply ssl certificate and private key to it.
 Security::ContextPointer
-Ssl::createSSLContext(Security::CertPointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port)
+Ssl::createSSLContext(Security::CertPointer & x509, Security::PrivateKeyPointer & pkey, Security::ServerOptions &options)
 {
-    Security::ContextPointer ctx(port.secure.createBlankContext());
+    Security::ContextPointer ctx(options.createBlankContext());
 
     if (!SSL_CTX_use_certificate(ctx.get(), x509.get()))
         return Security::ContextPointer();
@@ -889,62 +922,77 @@ Ssl::createSSLContext(Security::CertPointer & x509, Ssl::EVP_PKEY_Pointer & pkey
     if (!SSL_CTX_use_PrivateKey(ctx.get(), pkey.get()))
         return Security::ContextPointer();
 
-    if (!configureSslContext(ctx, port))
+    if (!options.updateContextConfig(ctx))
         return Security::ContextPointer();
 
     return ctx;
 }
 
 Security::ContextPointer
-Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port)
+Ssl::GenerateSslContextUsingPkeyAndCertFromMemory(const char * data, Security::ServerOptions &options, bool trusted)
 {
     Security::CertPointer cert;
-    Ssl::EVP_PKEY_Pointer pkey;
+    Security::PrivateKeyPointer pkey;
     if (!readCertAndPrivateKeyFromMemory(cert, pkey, data) || !cert || !pkey)
         return Security::ContextPointer();
 
-    return createSSLContext(cert, pkey, port);
+    Security::ContextPointer ctx(createSSLContext(cert, pkey, options));
+    if (ctx && trusted)
+        Ssl::chainCertificatesToSSLContext(ctx, options);
+    return ctx;
 }
 
 Security::ContextPointer
-Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port)
+Ssl::GenerateSslContext(CertificateProperties const &properties, Security::ServerOptions &options, bool trusted)
 {
     Security::CertPointer cert;
-    Ssl::EVP_PKEY_Pointer pkey;
+    Security::PrivateKeyPointer pkey;
     if (!generateSslCertificate(cert, pkey, properties) || !cert || !pkey)
         return Security::ContextPointer();
 
-    return createSSLContext(cert, pkey, port);
+    Security::ContextPointer ctx(createSSLContext(cert, pkey, options));
+    if (ctx && trusted)
+        Ssl::chainCertificatesToSSLContext(ctx, options);
+    return ctx;
 }
 
 void
-Ssl::chainCertificatesToSSLContext(Security::ContextPointer &ctx, AnyP::PortCfg &port)
+Ssl::chainCertificatesToSSLContext(Security::ContextPointer &ctx, Security::ServerOptions &options)
 {
     assert(ctx);
     // Add signing certificate to the certificates chain
-    X509 *signingCert = port.signingCert.get();
+    X509 *signingCert = options.signingCa.cert.get();
     if (SSL_CTX_add_extra_chain_cert(ctx.get(), signingCert)) {
         // increase the certificate lock
         X509_up_ref(signingCert);
     } else {
-        const int ssl_error = ERR_get_error();
+        const auto ssl_error = ERR_get_error();
         debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << Security::ErrorString(ssl_error));
     }
-    Ssl::addChainToSslContext(ctx, port.certsToChain.get());
+
+    for (auto cert : options.signingCa.chain) {
+        if (SSL_CTX_add_extra_chain_cert(ctx.get(), cert.get())) {
+            // increase the certificate lock
+            X509_up_ref(cert.get());
+        } else {
+            const auto error = ERR_get_error();
+            debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL dynamic context chain: " << Security::ErrorString(error));
+        }
+    }
 }
 
 void
 Ssl::configureUnconfiguredSslContext(Security::ContextPointer &ctx, Ssl::CertSignAlgorithm signAlgorithm,AnyP::PortCfg &port)
 {
     if (ctx && signAlgorithm == Ssl::algSignTrusted)
-        Ssl::chainCertificatesToSSLContext(ctx, port);
+        Ssl::chainCertificatesToSSLContext(ctx, port.secure);
 }
 
 bool
 Ssl::configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port)
 {
     Security::CertPointer cert;
-    Ssl::EVP_PKEY_Pointer pkey;
+    Security::PrivateKeyPointer pkey;
     if (!generateSslCertificate(cert, pkey, properties))
         return false;
 
@@ -967,7 +1015,7 @@ bool
 Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port)
 {
     Security::CertPointer cert;
-    Ssl::EVP_PKEY_Pointer pkey;
+    Security::PrivateKeyPointer pkey;
     if (!readCertAndPrivateKeyFromMemory(cert, pkey, data))
         return false;
 
@@ -984,7 +1032,7 @@ Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::Po
 }
 
 bool
-Ssl::verifySslCertificate(Security::ContextPointer &ctx, CertificateProperties const &properties)
+Ssl::verifySslCertificate(const Security::ContextPointer &ctx, CertificateProperties const &properties)
 {
 #if HAVE_SSL_CTX_GET0_CERTIFICATE
     X509 * cert = SSL_CTX_get0_certificate(ctx.get());
@@ -1003,54 +1051,33 @@ Ssl::verifySslCertificate(Security::ContextPointer &ctx, CertificateProperties c
 #endif
     if (!cert)
         return false;
-    ASN1_TIME * time_notBefore = X509_get_notBefore(cert);
-    ASN1_TIME * time_notAfter = X509_get_notAfter(cert);
-    bool ret = (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0);
-    if (!ret)
-        return false;
-
-    return certificateMatchesProperties(cert, properties);
+    const auto time_notBefore = X509_getm_notBefore(cert);
+    const auto time_notAfter = X509_getm_notAfter(cert);
+    return (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0);
 }
 
-bool
+void
 Ssl::setClientSNI(SSL *ssl, const char *fqdn)
 {
+    const Ip::Address test(fqdn);
+    if (!test.isAnyAddr())
+        return; // raw IP is inappropriate for SNI
+
     //The SSL_CTRL_SET_TLSEXT_HOSTNAME is a openssl macro which indicates
     // if the TLS servername extension (SNI) is enabled in openssl library.
 #if defined(SSL_CTRL_SET_TLSEXT_HOSTNAME)
     if (!SSL_set_tlsext_host_name(ssl, fqdn)) {
-        const int ssl_error = ERR_get_error();
+        const auto ssl_error = ERR_get_error();
         debugs(83, 3,  "WARNING: unable to set TLS servername extension (SNI): " <<
                Security::ErrorString(ssl_error) << "\n");
-        return false;
     }
-    return true;
 #else
-    debugs(83, 7,  "no support for TLS servername extension (SNI)\n");
-    return false;
+    debugs(83, 7,  "no support for TLS servername extension (SNI)");
 #endif
 }
 
-void
-Ssl::addChainToSslContext(Security::ContextPointer &ctx, STACK_OF(X509) *chain)
-{
-    if (!chain)
-        return;
-
-    for (int i = 0; i < sk_X509_num(chain); ++i) {
-        X509 *cert = sk_X509_value(chain, i);
-        if (SSL_CTX_add_extra_chain_cert(ctx.get(), cert)) {
-            // increase the certificate lock
-            X509_up_ref(cert);
-        } else {
-            const int ssl_error = ERR_get_error();
-            debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(ssl_error));
-        }
-    }
-}
-
-static const char *
-hasAuthorityInfoAccessCaIssuers(X509 *cert)
+const char *
+Ssl::findIssuerUri(X509 *cert)
 {
     AUTHORITY_INFO_ACCESS *info;
     if (!cert)
@@ -1068,11 +1095,7 @@ hasAuthorityInfoAccessCaIssuers(X509 *cert)
             if (ad->location->type == GEN_URI) {
                 xstrncpy(uri,
                          reinterpret_cast<const char *>(
-#if HAVE_LIBCRYPTO_ASN1_STRING_GET0_DATA
                              ASN1_STRING_get0_data(ad->location->d.uniformResourceIdentifier)
-#else
-                             ASN1_STRING_data(ad->location->d.uniformResourceIdentifier)
-#endif
                          ),
                          sizeof(uri));
             }
@@ -1126,151 +1149,196 @@ findCertIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
 }
 
 /// slowly find the issuer certificate of a given cert using linear search
-static bool
-findCertIssuer(Security::CertList const &list, X509 *cert)
+static X509 *
+sk_x509_findIssuer(const STACK_OF(X509) *sk, X509 *cert)
 {
-    for (Security::CertList::const_iterator it = list.begin(); it != list.end(); ++it) {
-        if (X509_check_issued(it->get(), cert) == X509_V_OK)
-            return true;
+    if (!sk)
+        return nullptr;
+
+    const auto certCount = sk_X509_num(sk);
+    for (int i = 0; i < certCount; ++i) {
+        const auto issuer = sk_X509_value(sk, i);
+        if (X509_check_issued(issuer, cert) == X509_V_OK)
+            return issuer;
     }
-    return false;
+    return nullptr;
 }
 
-const char *
-Ssl::uriOfIssuerIfMissing(X509 *cert, Security::CertList const &serverCertificates)
+/// finds issuer of a given certificate in CA store of the given connContext
+/// \returns the cert issuer (after increasing its reference count) or nil
+static X509 *
+findIssuerInCaDb(X509 *cert, const Security::ContextPointer &connContext)
 {
-    if (!cert || !serverCertificates.size())
+    if (!connContext)
         return nullptr;
 
-    if (!findCertIssuer(serverCertificates, cert)) {
-        //if issuer is missing
-        if (!findCertIssuerFast(SquidUntrustedCerts, cert)) {
-            // and issuer not found in local untrusted certificates database
-            if (const char *issuerUri = hasAuthorityInfoAccessCaIssuers(cert)) {
-                // There is a URI where we can download a certificate.
-                return issuerUri;
-            }
+    X509_STORE_CTX *storeCtx = X509_STORE_CTX_new();
+    if (!storeCtx) {
+        debugs(83, DBG_IMPORTANT, "Failed to allocate STORE_CTX object");
+        return nullptr;
+    }
+
+    X509 *issuer = nullptr;
+    X509_STORE *store = SSL_CTX_get_cert_store(connContext.get());
+    if (X509_STORE_CTX_init(storeCtx, store, nullptr, nullptr)) {
+        const auto ret = X509_STORE_CTX_get1_issuer(&issuer, storeCtx, cert);
+        if (ret > 0) {
+            assert(issuer);
+            char buffer[256];
+            debugs(83, 5, "found " << X509_NAME_oneline(X509_get_subject_name(issuer), buffer, sizeof(buffer)));
+        } else {
+            debugs(83, ret < 0 ? 2 : 3, "not found or failure: " << ret);
+            assert(!issuer);
         }
+    } else {
+        const auto ssl_error = ERR_get_error();
+        debugs(83, DBG_IMPORTANT, "Failed to initialize STORE_CTX object: " << Security::ErrorString(ssl_error));
     }
-    return nullptr;
+
+    X509_STORE_CTX_free(storeCtx);
+
+    return issuer;
 }
 
-void
-Ssl::missingChainCertificatesUrls(std::queue<SBuf> &URIs, Security::CertList const &serverCertificates)
+Security::CertPointer
+Ssl::findIssuerCertificate(X509 *cert, const STACK_OF(X509) *serverCertificates, const Security::ContextPointer &context)
 {
-    if (!serverCertificates.size())
-        return;
+    Must(cert);
 
-    for (const auto &i : serverCertificates) {
-        if (const char *issuerUri = uriOfIssuerIfMissing(i.get(), serverCertificates))
-            URIs.push(SBuf(issuerUri));
+    // check certificate chain, if any
+    if (const auto issuer = serverCertificates ? sk_x509_findIssuer(serverCertificates, cert) : nullptr) {
+        X509_up_ref(issuer);
+        return Security::CertPointer(issuer);
     }
-}
 
-void
-Ssl::SSL_add_untrusted_cert(SSL *ssl, X509 *cert)
-{
-    STACK_OF(X509) *untrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
-    if (!untrustedStack) {
-        untrustedStack = sk_X509_new_null();
-        if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain, untrustedStack)) {
-            sk_X509_pop_free(untrustedStack, X509_free);
-            throw TextException("Failed to attach untrusted certificates chain");
-        }
+    // check untrusted certificates
+    if (const auto issuer = findCertIssuerFast(SquidUntrustedCerts, cert)) {
+        X509_up_ref(issuer);
+        return Security::CertPointer(issuer);
     }
-    sk_X509_push(untrustedStack, cert);
+
+    // check trusted CA certificates
+    if (const auto issuer = findIssuerInCaDb(cert, context)) {
+        // no X509_up_ref(issuer) because findIssuerInCaDb() ups reference count
+        return Security::CertPointer(issuer);
+    }
+
+    return Security::CertPointer(nullptr);
 }
 
-/// Search for the issuer certificate of cert in sk list.
-static X509 *
-sk_x509_findIssuer(STACK_OF(X509) *sk, X509 *cert)
+bool
+Ssl::missingChainCertificatesUrls(std::queue<SBuf> &URIs, const STACK_OF(X509) &serverCertificates, const Security::ContextPointer &context)
 {
-    if (!sk)
-        return NULL;
+    for (int i = 0; i < sk_X509_num(&serverCertificates); ++i) {
+        const auto cert = sk_X509_value(&serverCertificates, i);
 
-    const int skItemsNum = sk_X509_num(sk);
-    for (int i = 0; i < skItemsNum; ++i) {
-        X509 *issuer = sk_X509_value(sk, i);
-        if (X509_check_issued(issuer, cert) == X509_V_OK)
-            return issuer;
+        if (findIssuerCertificate(cert, &serverCertificates, context))
+            continue;
+
+        if (const auto issuerUri = findIssuerUri(cert)) {
+            URIs.push(SBuf(issuerUri));
+        } else {
+            static char name[2048];
+            debugs(83, 3, "Issuer certificate for " <<
+                   X509_NAME_oneline(X509_get_subject_name(cert), name, sizeof(name)) <<
+                   " is missing and its URI is not provided");
+        }
     }
-    return NULL;
+
+    debugs(83, (URIs.empty() ? 3 : 5), "found: " << URIs.size());
+    return !URIs.empty();
 }
 
 /// add missing issuer certificates to untrustedCerts
 static void
-completeIssuers(X509_STORE_CTX *ctx, STACK_OF(X509) *untrustedCerts)
+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");
+    debugs(83, 2,  "completing " << sk_X509_num(&untrustedCerts) <<
+           " OpenSSL untrusted certs using " << SquidUntrustedCerts.size() <<
+           " configured untrusted certificates");
 
-#if HAVE_LIBCRYPTO_X509_VERIFY_PARAM_GET_DEPTH
     const X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
     int depth = X509_VERIFY_PARAM_get_depth(param);
-#else
-    int depth = ctx->param->depth;
-#endif
-    X509 *current = X509_STORE_CTX_get0_cert(ctx);
+    Security::CertPointer current;
+    current.resetAndLock(X509_STORE_CTX_get0_cert(ctx));
     int i = 0;
     for (i = 0; current && (i < depth); ++i) {
-        if (X509_check_issued(current, current) == X509_V_OK) {
+        if (X509_check_issued(current.get(), current.get()) == X509_V_OK) {
             // either ctx->cert is itself self-signed or untrustedCerts
-            // aready contain the self-signed current certificate
+            // already contain the self-signed current certificate
             break;
         }
 
         // untrustedCerts is short, not worth indexing
-        X509 *issuer = sk_x509_findIssuer(untrustedCerts, current);
-        if (!issuer) {
-            if ((issuer = findCertIssuerFast(SquidUntrustedCerts, current)))
-                sk_X509_push(untrustedCerts, issuer);
-        }
+        const Security::ContextPointer nullCtx;
+        auto issuer = Ssl::findIssuerCertificate(current.get(), &untrustedCerts, nullCtx);
         current = issuer;
+        if (issuer)
+            sk_X509_push(&untrustedCerts, issuer.release());
     }
 
     if (i >= depth)
         debugs(83, 2,  "exceeded the maximum certificate chain length: " << depth);
 }
 
-/// OpenSSL certificate validation callback.
+/// Validates certificates while consulting sslproxy_foreign_intermediate_certs
+/// and, optionally, the given extra certificates.
+/// \returns whatever OpenSSL X509_verify_cert() returns
 static int
-untrustedToStoreCtx_cb(X509_STORE_CTX *ctx,void *data)
+VerifyCtxCertificates(X509_STORE_CTX *ctx, STACK_OF(X509) *extraCerts)
 {
-    debugs(83, 4,  "Try to use pre-downloaded intermediate certificates\n");
-
-    SSL *ssl = static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
-    STACK_OF(X509) *sslUntrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
-
     // 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
-#if HAVE_LIBCRYPTO_X509_STORE_CTX_GET0_UNTRUSTED
     STACK_OF(X509) *oldUntrusted = X509_STORE_CTX_get0_untrusted(ctx);
-#else
-    STACK_OF(X509) *oldUntrusted = ctx->untrusted;
-#endif
-    STACK_OF(X509) *sk = sk_X509_dup(oldUntrusted); // oldUntrusted is always not NULL
+    // X509_chain_up_ref() increments cert references _and_ dupes the stack
+    Ssl::X509_STACK_Pointer untrustedCerts(oldUntrusted ? X509_chain_up_ref(oldUntrusted) : sk_X509_new_null());
 
-    for (int i = 0; i < sk_X509_num(sslUntrustedStack); ++i) {
-        X509 *cert = sk_X509_value(sslUntrustedStack, i);
-        sk_X509_push(sk, cert);
+    if (extraCerts) {
+        for (int i = 0; i < sk_X509_num(extraCerts); ++i) {
+            const auto cert = sk_X509_value(extraCerts, i);
+            X509_up_ref(cert);
+            sk_X509_push(untrustedCerts.get(), cert);
+        }
     }
 
     // If the local untrusted certificates internal database is used
     // run completeIssuers to add missing certificates if possible.
     if (SquidUntrustedCerts.size() > 0)
-        completeIssuers(ctx, sk);
+        completeIssuers(ctx, *untrustedCerts);
 
-    X509_STORE_CTX_set_chain(ctx, sk); // No locking/unlocking, just sets ctx->untrusted
+    X509_STORE_CTX_set0_untrusted(ctx, untrustedCerts.get()); // No locking/unlocking, just sets ctx->untrusted
     int ret = X509_verify_cert(ctx);
-#if HAVE_LIBCRYPTO_X509_STORE_CTX_SET0_UNTRUSTED
-    X509_STORE_CTX_set0_untrusted(ctx, oldUntrusted);
-#else
-    X509_STORE_CTX_set_chain(ctx, oldUntrusted); // Set back the old untrusted list
-#endif
-    sk_X509_free(sk); // Release sk list
+    X509_STORE_CTX_set0_untrusted(ctx, oldUntrusted); // Set back the old untrusted list
     return ret;
 }
 
+/// \interface OpenSSL_vcb_disambiguation
+///
+/// OpenSSL has two very different concepts with nearly identical names:
+///
+/// a) A (replaceable) certificate verification function -- X509_verify_cert():
+///    This function drives the entire certificate verification algorithm.
+///    It can be called directly, but is usually called during SSL_connect().
+///    OpenSSL calls this function a "verification callback function".
+///    SSL_CTX_set_cert_verify_callback(3) replaces X509_verify_cert() default.
+///
+/// b) An (optional) certificate verification adjustment callback:
+///    This function, if set, is called at the end of (a) to adjust (a) results.
+///    It is never called directly, only from (a).
+///    OpenSSL calls this function a "verify_callback function".
+///    The SSL_CTX_set_verify(3) family of functions sets this function.
+
+/// Validates certificates while consulting sslproxy_foreign_intermediate_certs
+/// but without using any dynamically downloaded intermediate certificates.
+/// OpenSSL "verification callback function" (\ref OpenSSL_vcb_disambiguation)
+static int
+untrustedToStoreCtx_cb(X509_STORE_CTX *ctx, void *)
+{
+    debugs(83, 4, "Try to use pre-downloaded intermediate certificates");
+    return VerifyCtxCertificates(ctx, nullptr);
+}
+
 void
 Ssl::useSquidUntrusted(SSL_CTX *sslContext)
 {
@@ -1294,71 +1362,7 @@ Ssl::unloadSquidUntrusted()
     }
 }
 
-/**
- \ingroup ServerProtocolSSLInternal
- * Read certificate from file.
- * See also: static readSslX509Certificate function, gadgets.cc file
- */
-static X509 * readSslX509CertificatesChain(char const * certFilename,  STACK_OF(X509)* chain)
-{
-    if (!certFilename)
-        return NULL;
-    Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
-    if (!bio)
-        return NULL;
-    if (!BIO_read_filename(bio.get(), certFilename))
-        return NULL;
-    X509 *certificate = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL);
-
-    if (certificate && chain) {
-
-        if (X509_check_issued(certificate, certificate) == X509_V_OK)
-            debugs(83, 5, "Certificate is self-signed, will not be chained");
-        else {
-            // and add to the chain any other certificate exist in the file
-            while (X509 *ca = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL)) {
-                if (!sk_X509_push(chain, ca))
-                    debugs(83, DBG_IMPORTANT, "WARNING: unable to add CA certificate to cert chain");
-            }
-        }
-    }
-
-    return certificate;
-}
-
-void Ssl::readCertChainAndPrivateKeyFromFiles(Security::CertPointer & cert, EVP_PKEY_Pointer & pkey, X509_STACK_Pointer & chain, char const * certFilename, char const * keyFilename)
-{
-    if (keyFilename == NULL)
-        keyFilename = certFilename;
-
-    if (certFilename == NULL)
-        certFilename = keyFilename;
-
-    debugs(83, DBG_IMPORTANT, "Using certificate in " << certFilename);
-
-    if (!chain)
-        chain.reset(sk_X509_new_null());
-    if (!chain)
-        debugs(83, DBG_IMPORTANT, "WARNING: unable to allocate memory for cert chain");
-    // XXX: ssl_ask_password_cb needs SSL_CTX_set_default_passwd_cb_userdata()
-    // so this may not fully work iff Config.Program.ssl_password is set.
-    pem_password_cb *cb = ::Config.Program.ssl_password ? &ssl_ask_password_cb : NULL;
-    pkey.resetWithoutLocking(readSslPrivateKey(keyFilename, cb));
-    cert.resetWithoutLocking(readSslX509CertificatesChain(certFilename, chain.get()));
-    if (!cert) {
-        debugs(83, DBG_IMPORTANT, "WARNING: missing cert in '" << certFilename << "'");
-    } else if (!pkey) {
-        debugs(83, DBG_IMPORTANT, "WARNING: missing private key in '" << keyFilename << "'");
-    } else if (!X509_check_private_key(cert.get(), pkey.get())) {
-        debugs(83, DBG_IMPORTANT, "WARNING: X509_check_private_key() failed to verify signing cert");
-    } else
-        return; // everything is okay
-
-    pkey.reset();
-    cert.reset();
-}
-
-bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, EVP_PKEY_Pointer &untrustedPkey, Security::CertPointer const  &cert, EVP_PKEY_Pointer const & pkey)
+bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, Security::PrivateKeyPointer &untrustedPkey, Security::CertPointer const  &cert, Security::PrivateKeyPointer const & pkey)
 {
     // Generate the self-signed certificate, using a hard-coded subject prefix
     Ssl::CertificateProperties certProperties;
@@ -1381,112 +1385,115 @@ bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, EVP_PKEY_P
     return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties);
 }
 
-static int
-store_session_cb(SSL *ssl, SSL_SESSION *session)
+void Ssl::InRamCertificateDbKey(const Ssl::CertificateProperties &certProperties, SBuf &key)
 {
-    if (!Ssl::SessionCache)
-        return 0;
-
-    debugs(83, 5, "Request to store SSL Session ");
-
-    SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
-
-#if HAVE_LIBSSL_SSL_SESSION_GET_ID
-    unsigned int idlen;
-    const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
-#else
-    unsigned char *id = session->session_id;
-    unsigned int idlen = session->session_id_length;
-#endif
-    unsigned char key[MEMMAP_SLOT_KEY_SIZE];
-    // Session ids are of size 32bytes. They should always fit to a
-    // MemMap::Slot::key
-    assert(idlen <= MEMMAP_SLOT_KEY_SIZE);
-    memset(key, 0, sizeof(key));
-    memcpy(key, id, idlen);
-    int pos;
-    Ipc::MemMap::Slot *slotW = Ssl::SessionCache->openForWriting((const cache_key*)key, pos);
-    if (slotW) {
-        int lenRequired =  i2d_SSL_SESSION(session, NULL);
-        if (lenRequired <  MEMMAP_SLOT_DATA_SIZE) {
-            unsigned char *p = (unsigned char *)slotW->p;
-            lenRequired = i2d_SSL_SESSION(session, &p);
-            slotW->set(key, NULL, lenRequired, squid_curtime + Config.SSL.session_ttl);
+    bool origSignatureAsKey = false;
+    if (certProperties.mimicCert) {
+        if (auto *sig = Ssl::X509_get_signature(certProperties.mimicCert)) {
+            origSignatureAsKey = true;
+            key.append((const char *)sig->data, sig->length);
         }
-        Ssl::SessionCache->closeForWriting(pos);
-        debugs(83, 5, "wrote an ssl session entry of size " << lenRequired << " at pos " << pos);
     }
-    return 0;
+
+    if (!origSignatureAsKey || certProperties.setCommonName) {
+        // Use common name instead
+        key.append(certProperties.commonName.c_str());
+    }
+    key.append(certProperties.setCommonName ? '1' : '0');
+    key.append(certProperties.setValidAfter ? '1' : '0');
+    key.append(certProperties.setValidBefore ? '1' : '0');
+    key.append(certProperties.signAlgorithm != Ssl:: algSignEnd ? certSignAlgorithm(certProperties.signAlgorithm) : "-");
+    key.append(certProperties.signHash ? EVP_MD_name(certProperties.signHash) : "-");
+
+    if (certProperties.mimicCert) {
+        Ssl::BIO_Pointer bio(BIO_new_SBuf(&key));
+        ASN1_item_i2d_bio(ASN1_ITEM_rptr(X509), bio.get(), (ASN1_VALUE *)certProperties.mimicCert.get());
+    }
 }
 
-static void
-remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
+static int
+bio_sbuf_create(BIO* bio)
 {
-    if (!Ssl::SessionCache)
-        return ;
+    BIO_set_init(bio, 0);
+    BIO_set_data(bio, NULL);
+    return 1;
+}
 
-    debugs(83, 5, "Request to remove corrupted or not valid SSL Session ");
-    int pos;
-    Ipc::MemMap::Slot const *slot = Ssl::SessionCache->openForReading((const cache_key*)sessionID, pos);
-    if (slot == NULL)
-        return;
-    Ssl::SessionCache->closeForReading(pos);
-    // TODO:
-    // What if we are not able to remove the session?
-    // Maybe schedule a job to remove it later?
-    // For now we just have an invalid entry in cache until will be expired
-    // The openSSL will reject it when we try to use it
-    Ssl::SessionCache->free(pos);
-}
-
-static SSL_SESSION *
-#if SQUID_USE_CONST_SSL_SESSION_CBID
-get_session_cb(SSL *, const unsigned char *sessionID, int len, int *copy)
-#else
-get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
-#endif
+static int
+bio_sbuf_destroy(BIO* bio)
 {
-    if (!Ssl::SessionCache)
-        return NULL;
+    if (!bio)
+        return 0;
+    return 1;
+}
 
-    SSL_SESSION *session = NULL;
-    const unsigned int *p;
-    p = (unsigned int *)sessionID;
-    debugs(83, 5, "Request to search for SSL Session of len:" <<
-           len << p[0] << ":" << p[1]);
-
-    int pos;
-    Ipc::MemMap::Slot const *slot = Ssl::SessionCache->openForReading((const cache_key*)sessionID, pos);
-    if (slot != NULL) {
-        if (slot->expire > squid_curtime) {
-            const unsigned char *ptr = slot->p;
-            session = d2i_SSL_SESSION(NULL, &ptr, slot->pSize);
-            debugs(83, 5, "Session retrieved from cache at pos " << pos);
-        } else
-            debugs(83, 5, "Session in cache expired");
-        Ssl::SessionCache->closeForReading(pos);
-    }
+int
+bio_sbuf_write(BIO* bio, const char* data, int len)
+{
+    SBuf *buf = static_cast<SBuf *>(BIO_get_data(bio));
+    // TODO: Convert exceptions into BIO errors
+    buf->append(data, len);
+    return len;
+}
 
-    if (!session)
-        debugs(83, 5, "Failed to retrieved from cache\n");
+int
+bio_sbuf_puts(BIO* bio, const char* data)
+{
+    // TODO: use bio_sbuf_write() instead
+    SBuf *buf = static_cast<SBuf *>(BIO_get_data(bio));
+    size_t oldLen = buf->length();
+    buf->append(data);
+    return buf->length() - oldLen;
+}
 
-    // With the parameter copy the callback can require the SSL engine
-    // to increment the reference count of the SSL_SESSION object, Normally
-    // the reference count is not incremented and therefore the session must
-    // not be explicitly freed with SSL_SESSION_free(3).
-    *copy = 0;
-    return session;
+long
+bio_sbuf_ctrl(BIO* bio, int cmd, long num, void* ptr) {
+    SBuf *buf = static_cast<SBuf *>(BIO_get_data(bio));
+    switch (cmd) {
+    case BIO_CTRL_RESET:
+        // TODO: Convert exceptions into BIO errors
+        buf->clear();
+        return 1;
+    case BIO_CTRL_FLUSH:
+        return 1;
+    default:
+        return 0;
+    }
 }
 
-void
-Ssl::SetSessionCallbacks(Security::ContextPointer &ctx)
+BIO *Ssl::BIO_new_SBuf(SBuf *buf)
 {
-    if (Ssl::SessionCache) {
-        SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL);
-        SSL_CTX_sess_set_new_cb(ctx.get(), store_session_cb);
-        SSL_CTX_sess_set_remove_cb(ctx.get(), remove_session_cb);
-        SSL_CTX_sess_set_get_cb(ctx.get(), get_session_cb);
+#if HAVE_LIBCRYPTO_BIO_METH_NEW
+    static BIO_METHOD *BioSBufMethods = nullptr;
+    if (!BioSBufMethods) {
+        BioSBufMethods = BIO_meth_new(BIO_TYPE_MEM, "Squid-SBuf");
+        BIO_meth_set_write(BioSBufMethods, bio_sbuf_write);
+        BIO_meth_set_read(BioSBufMethods, nullptr);
+        BIO_meth_set_puts(BioSBufMethods, bio_sbuf_puts);
+        BIO_meth_set_gets(BioSBufMethods, nullptr);
+        BIO_meth_set_ctrl(BioSBufMethods, bio_sbuf_ctrl);
+        BIO_meth_set_create(BioSBufMethods, bio_sbuf_create);
+        BIO_meth_set_destroy(BioSBufMethods, bio_sbuf_destroy);
     }
+#else
+    static BIO_METHOD *BioSBufMethods = new BIO_METHOD({
+        BIO_TYPE_MEM,
+        "Squid SBuf",
+        bio_sbuf_write,
+        nullptr,
+        bio_sbuf_puts,
+        nullptr,
+        bio_sbuf_ctrl,
+        bio_sbuf_create,
+        bio_sbuf_destroy,
+        NULL
+    });
+#endif
+    BIO *bio = BIO_new(BioSBufMethods);
+    Must(bio);
+    BIO_set_data(bio, buf);
+    BIO_set_init(bio, 1);
+    return bio;
 }
 
 #endif /* USE_OPENSSL */