]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ssl/support.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / ssl / support.cc
index 5406dd0a6a0a35f045ee0f70fab7c5aa9195fca3..3ec51fdfad5476814b6ff1b4090aba53bc2119bd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 1996-2016 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/bio.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;
 
 const EVP_MD *Ssl::DefaultSignHash = NULL;
 
-const char *Ssl::BumpModeStr[] = {
+std::vector<const char *> Ssl::BumpModeStr = {
     "none",
     "client-first",
     "server-first",
@@ -51,9 +51,8 @@ const char *Ssl::BumpModeStr[] = {
     "stare",
     "bump",
     "splice",
-    "terminate",
-    /*"err",*/
-    NULL
+    "terminate"
+    /*,"err"*/
 };
 
 /**
@@ -61,15 +60,14 @@ 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))
@@ -91,27 +89,43 @@ 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);
     }
 }
 
-/// \ingroup ServerProtocolSSLInternal
+#if HAVE_LIBSSL_SSL_CTX_SET_TMP_RSA_CALLBACK
 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;
@@ -120,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;
@@ -138,7 +157,7 @@ ssl_temp_rsa_cb(SSL * ssl, int anInt, int keylen)
     }
 
     if (newkey) {
-        if (do_debug(83, 5))
+        if (Debug::Enabled(83, 5))
             PEM_write_RSAPrivateKey(debug_log, rsa, NULL, NULL, 0, NULL, NULL);
 
         debugs(83, DBG_IMPORTANT, "Generated ephemeral RSA key of length " << keylen);
@@ -146,6 +165,16 @@ ssl_temp_rsa_cb(SSL * ssl, int anInt, int keylen)
 
     return rsa;
 }
+#endif
+
+void
+Ssl::MaybeSetupRsaCallback(Security::ContextPointer &ctx)
+{
+#if HAVE_LIBSSL_SSL_CTX_SET_TMP_RSA_CALLBACK
+    debugs(83, 9, "Setting RSA key generation callback.");
+    SSL_CTX_set_tmp_rsa_callback(ctx.get(), ssl_temp_rsa_cb);
+#endif
+}
 
 int Ssl::asn1timeToString(ASN1_TIME *tm, char *buf, int len)
 {
@@ -202,9 +231,12 @@ static int check_domain( void *check_data, ASN1_STRING *cn_data)
     char cn[1024];
     const char *server = (const char *)check_data;
 
-    if (cn_data->length > (int)sizeof(cn) - 1) {
+    if (cn_data->length == 0)
+        return 1; // zero length cn, ignore
+
+    if (cn_data->length > (int)sizeof(cn) - 1)
         return 1; //if does not fit our buffer just ignore
-    }
+
     char *s = reinterpret_cast<char*>(cn_data->data);
     char *d = cn;
     for (int i = 0; i < cn_data->length; ++i, ++d, ++s) {
@@ -214,7 +246,7 @@ static int check_domain( void *check_data, ASN1_STRING *cn_data)
     }
     cn[cn_data->length] = '\0';
     debugs(83, 4, "Verifying server domain " << server << " to certificate name/subjectAltName " << cn);
-    return matchDomainName(server, cn[0] == '*' ? cn + 1 : cn);
+    return matchDomainName(server, (cn[0] == '*' ? cn + 1 : cn), mdnRejectSubsubDomains);
 }
 
 bool Ssl::checkX509ServerValidity(X509 *cert, const char *server)
@@ -222,24 +254,25 @@ bool Ssl::checkX509ServerValidity(X509 *cert, const char *server)
     return matchX509CommonNames(cert, (void *)server, check_domain);
 }
 
-/// \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)
 {
     // preserve original ctx->error before SSL_ calls can overwrite it
-    Ssl::ssl_error_t error_no = ok ? SSL_ERROR_NONE : ctx->error;
+    Security::ErrorCode error_no = ok ? SSL_ERROR_NONE : X509_STORE_CTX_get_error(ctx);
 
     char buffer[256] = "";
     SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
-    Security::ContextPtr sslctx = SSL_get_SSL_CTX(ssl);
+    SSL_CTX *sslctx = SSL_get_SSL_CTX(ssl);
     SBuf *server = (SBuf *)SSL_get_ex_data(ssl, ssl_ex_index_server);
     void *dont_verify_domain = SSL_CTX_get_ex_data(sslctx, ssl_ctx_ex_index_dont_verify_domain);
     ACLChecklist *check = (ACLChecklist*)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check);
     X509 *peeked_cert = (X509 *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_peeked_cert);
-    X509 *peer_cert = ctx->cert;
+    Security::CertPointer peer_cert;
+    peer_cert.resetAndLock(X509_STORE_CTX_get0_cert(ctx));
 
-    X509_NAME_oneline(X509_get_subject_name(peer_cert), buffer,
-                      sizeof(buffer));
+    X509_NAME_oneline(X509_get_subject_name(peer_cert.get()), buffer, sizeof(buffer));
 
     // detect infinite loops
     uint32_t *validationCounter = static_cast<uint32_t *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_validation_counter));
@@ -262,8 +295,8 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
         debugs(83, 5, "SSL Certificate signature OK: " << buffer);
 
         // Check for domain mismatch only if the current certificate is the peer certificate.
-        if (!dont_verify_domain && server && peer_cert == X509_STORE_CTX_get_current_cert(ctx)) {
-            if (!Ssl::checkX509ServerValidity(peer_cert, server->c_str())) {
+        if (!dont_verify_domain && server && peer_cert.get() == X509_STORE_CTX_get_current_cert(ctx)) {
+            if (!Ssl::checkX509ServerValidity(peer_cert.get(), server->c_str())) {
                 debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << buffer << " does not match domainname " << server);
                 ok = 0;
                 error_no = SQUID_X509_V_ERR_DOMAIN_MISMATCH;
@@ -273,29 +306,40 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
 
     if (ok && peeked_cert) {
         // Check whether the already peeked certificate matches the new one.
-        if (X509_cmp(peer_cert, peeked_cert) != 0) {
+        if (X509_cmp(peer_cert.get(), peeked_cert) != 0) {
             debugs(83, 2, "SQUID_X509_V_ERR_CERT_CHANGE: Certificate " << buffer << " does not match peeked certificate");
             ok = 0;
             error_no =  SQUID_X509_V_ERR_CERT_CHANGE;
         }
     }
 
+    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) {
-        X509 *broken_cert =  X509_STORE_CTX_get_current_cert(ctx);
+        Security::CertPointer broken_cert;
+        broken_cert.resetAndLock(X509_STORE_CTX_get_current_cert(ctx));
         if (!broken_cert)
             broken_cert = peer_cert;
 
-        Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors));
+        Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors));
+        const int depth = X509_STORE_CTX_get_error_depth(ctx);
         if (!errs) {
-            const int depth = X509_STORE_CTX_get_error_depth(ctx);
-            errs = new Ssl::CertErrors(Ssl::CertError(error_no, broken_cert, depth));
+            errs = new Security::CertErrors(Security::CertError(error_no, broken_cert, depth));
             if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_errors,  (void *)errs)) {
                 debugs(83, 2, "Failed to set ssl error_no in ssl_verify_cb: Certificate " << buffer);
                 delete errs;
                 errs = NULL;
             }
         } else // remember another error number
-            errs->push_back_unique(Ssl::CertError(error_no, broken_cert));
+            errs->push_back_unique(Security::CertError(error_no, broken_cert, depth));
 
         if (const char *err_descr = Ssl::GetErrorDescr(error_no))
             debugs(83, 5, err_descr << ": " << buffer);
@@ -308,9 +352,9 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
             if (check) {
                 ACLFilledChecklist *filledCheck = Filled(check);
                 assert(!filledCheck->sslErrors);
-                filledCheck->sslErrors = new Ssl::CertErrors(Ssl::CertError(error_no, broken_cert));
-                filledCheck->serverCert.resetAndLock(peer_cert);
-                if (check->fastCheck() == ACCESS_ALLOWED) {
+                filledCheck->sslErrors = new Security::CertErrors(Security::CertError(error_no, broken_cert));
+                filledCheck->serverCert = peer_cert;
+                if (check->fastCheck().allowed()) {
                     debugs(83, 3, "bypassing SSL error " << error_no << " in " << buffer);
                     ok = 1;
                 } else {
@@ -318,10 +362,10 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
                 }
                 delete filledCheck->sslErrors;
                 filledCheck->sslErrors = NULL;
-                filledCheck->serverCert.reset(NULL);
+                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;
             }
@@ -340,29 +384,188 @@ ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
     if (!ok && !SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) ) {
 
         // Find the broken certificate. It may be intermediate.
-        X509 *broken_cert = peer_cert; // reasonable default if search fails
+        Security::CertPointer broken_cert(peer_cert); // reasonable default if search fails
         // Our SQUID_X509_V_ERR_DOMAIN_MISMATCH implies peer_cert is at fault.
         if (error_no != SQUID_X509_V_ERR_DOMAIN_MISMATCH) {
-            if (X509 *last_used_cert = X509_STORE_CTX_get_current_cert(ctx))
-                broken_cert = last_used_cert;
+            if (auto *last_used_cert = X509_STORE_CTX_get_current_cert(ctx))
+                broken_cert.resetAndLock(last_used_cert);
         }
 
-        Ssl::ErrorDetail *errDetail =
-            new Ssl::ErrorDetail(error_no, peer_cert, broken_cert);
-
-        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
+ssl_dupAclChecklist(CRYPTO_EX_DATA *, const CRYPTO_EX_DATA *, void *,
+                    int, long, void *)
+#else
 static int
 ssl_dupAclChecklist(CRYPTO_EX_DATA *, CRYPTO_EX_DATA *, void *,
                     int, long, void *)
+#endif
 {
     // We do not support duplication of ACLCheckLists.
     // If duplication is needed, we can count copies with cbdata.
@@ -383,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;
 }
 
@@ -391,7 +594,7 @@ static void
 ssl_free_SslErrors(void *, void *ptr, CRYPTO_EX_DATA *,
                    int, long, void *)
 {
-    Ssl::CertErrors *errs = static_cast <Ssl::CertErrors*>(ptr);
+    Security::CertErrors *errs = static_cast <Security::CertErrors*>(ptr);
     delete errs;
 }
 
@@ -433,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)
 {
@@ -441,18 +652,18 @@ 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();
-            fatalf("Failed to initialise SSL engine: %s\n", ERR_error_string(ssl_error, NULL));
+            const auto ssl_error = ERR_get_error();
+            fatalf("Failed to initialise SSL engine: %s\n", Security::ErrorString(ssl_error));
         }
     }
 #else
@@ -473,179 +684,32 @@ 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);
+    ssl_ex_index_verify_callback_parameters = SSL_get_ex_new_index(0, (void *) "verify_callback_parameters", nullptr, nullptr, &ssl_free_VerifyCallbackParameters);
 }
 
-#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
-static void
-ssl_info_cb(const SSL *ssl, int where, int ret)
-{
-    (void)ret;
-    if ((where & SSL_CB_HANDSHAKE_DONE) != 0) {
-        // disable renegotiation (CVE-2009-3555)
-        ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
-    }
-}
-#endif
-
-static bool
-configureSslContext(Security::ContextPtr sslContext, AnyP::PortCfg &port)
+bool
+Ssl::InitServerContext(Security::ContextPointer &ctx, AnyP::PortCfg &port)
 {
-    int ssl_error;
-    SSL_CTX_set_options(sslContext, port.secure.parsedOptions);
-
-#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
-    SSL_CTX_set_info_callback(sslContext, ssl_info_cb);
-#endif
-
-    if (port.sslContextSessionId)
-        SSL_CTX_set_session_id_context(sslContext, (const unsigned char *)port.sslContextSessionId, strlen(port.sslContextSessionId));
-
-    if (port.secure.parsedFlags & SSL_FLAG_NO_SESSION_REUSE) {
-        SSL_CTX_set_session_cache_mode(sslContext, SSL_SESS_CACHE_OFF);
-    }
-
-    if (Config.SSL.unclean_shutdown) {
-        debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation).");
-
-        SSL_CTX_set_quiet_shutdown(sslContext, 1);
-    }
-
-    if (!port.secure.sslCipher.isEmpty()) {
-        debugs(83, 5, "Using chiper suite " << port.secure.sslCipher << ".");
-
-        if (!SSL_CTX_set_cipher_list(sslContext, port.secure.sslCipher.c_str())) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to set SSL cipher suite '" << port.secure.sslCipher << "': " << ERR_error_string(ssl_error, NULL));
-            return false;
-        }
-    }
-
-    debugs(83, 9, "Setting RSA key generation callback.");
-    SSL_CTX_set_tmp_rsa_callback(sslContext, ssl_temp_rsa_cb);
-
-    port.secure.updateContextEecdh(sslContext);
-    port.secure.updateContextCa(sslContext);
-
-    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(sslContext, clientca);
-        } else {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to dupe the client CA list: " << ERR_error_string(ssl_error, NULL));
-            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(sslContext, SSL_VERIFY_NONE, NULL);
-        } else {
-            debugs(83, 9, "Requiring client certificates.");
-            SSL_CTX_set_verify(sslContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb);
-        }
-
-        port.secure.updateContextCrl(sslContext);
-
-    } else {
-        debugs(83, 9, "Not requiring any client certificates");
-        SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL);
-    }
-
-    if (port.secure.parsedFlags & SSL_FLAG_DONT_VERIFY_DOMAIN)
-        SSL_CTX_set_ex_data(sslContext, ssl_ctx_ex_index_dont_verify_domain, (void *) -1);
-
-    Ssl::SetSessionCallbacks(sslContext);
+    if (!ctx)
+        return false;
 
     return true;
 }
 
-Security::ContextPtr
-sslCreateServerContext(AnyP::PortCfg &port)
-{
-    Security::ContextPtr sslContext(port.secure.createBlankContext());
-    if (!sslContext)
-        return nullptr;
-
-    if (!SSL_CTX_use_certificate(sslContext, 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 << "': " << ERR_error_string(ssl_error, NULL));
-        SSL_CTX_free(sslContext);
-        return NULL;
-    }
-
-    if (!SSL_CTX_use_PrivateKey(sslContext, 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 << "': " << ERR_error_string(ssl_error, NULL));
-        SSL_CTX_free(sslContext);
-        return NULL;
-    }
-
-    Ssl::addChainToSslContext(sslContext, port.certsToChain.get());
-
-    /* Alternate code;
-        debugs(83, DBG_IMPORTANT, "Using certificate in " << certfile);
-
-        if (!SSL_CTX_use_certificate_chain_file(sslContext, certfile)) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL certificate '" << certfile << "': " << ERR_error_string(ssl_error, NULL));
-            SSL_CTX_free(sslContext);
-            return NULL;
-        }
-
-        debugs(83, DBG_IMPORTANT, "Using private key in " << keyfile);
-        ssl_ask_password(sslContext, keyfile);
-
-        if (!SSL_CTX_use_PrivateKey_file(sslContext, keyfile, SSL_FILETYPE_PEM)) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL private key '" << keyfile << "': " << ERR_error_string(ssl_error, NULL));
-            SSL_CTX_free(sslContext);
-            return NULL;
-        }
-
-        debugs(83, 5, "Comparing private and public SSL keys.");
-
-        if (!SSL_CTX_check_private_key(sslContext)) {
-            ssl_error = ERR_get_error();
-            debugs(83, DBG_CRITICAL, "ERROR: SSL private key '" << certfile << "' does not match public key '" <<
-                   keyfile << "': " << ERR_error_string(ssl_error, NULL));
-            SSL_CTX_free(sslContext);
-            return NULL;
-        }
-    */
-
-    if (!configureSslContext(sslContext, port)) {
-        debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context");
-        SSL_CTX_free(sslContext);
-        return NULL;
-    }
-
-    return sslContext;
-}
-
-Security::ContextPtr
-sslCreateClientContext(Security::PeerOptions &peer, long options, long fl)
+bool
+Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, Security::ParsedPortFlags fl)
 {
-    Security::ContextPtr sslContext(peer.createBlankContext());
-    if (!sslContext)
-        return nullptr;
-
-    SSL_CTX_set_options(sslContext, options);
-
-#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
-    SSL_CTX_set_info_callback(sslContext, ssl_info_cb);
-#endif
+    if (!ctx)
+        return false;
 
     if (!peer.sslCipher.isEmpty()) {
         debugs(83, 5, "Using chiper suite " << peer.sslCipher << ".");
 
         const char *cipher = peer.sslCipher.c_str();
-        if (!SSL_CTX_set_cipher_list(sslContext, cipher)) {
-            const int ssl_error = ERR_get_error();
+        if (!SSL_CTX_set_cipher_list(ctx.get(), cipher)) {
+            const auto ssl_error = ERR_get_error();
             fatalf("Failed to set SSL cipher suite '%s': %s\n",
-                   cipher, ERR_error_string(ssl_error, NULL));
+                   cipher, Security::ErrorString(ssl_error));
         }
     }
 
@@ -656,90 +720,37 @@ sslCreateClientContext(Security::PeerOptions &peer, long options, long fl)
             debugs(83, DBG_IMPORTANT, "Using certificate in " << keys.certFile);
 
             const char *certfile = keys.certFile.c_str();
-            if (!SSL_CTX_use_certificate_chain_file(sslContext, certfile)) {
-                const int ssl_error = ERR_get_error();
+            if (!SSL_CTX_use_certificate_chain_file(ctx.get(), certfile)) {
+                const auto ssl_error = ERR_get_error();
                 fatalf("Failed to acquire SSL certificate '%s': %s\n",
-                       certfile, ERR_error_string(ssl_error, NULL));
+                       certfile, Security::ErrorString(ssl_error));
             }
 
             debugs(83, DBG_IMPORTANT, "Using private key in " << keys.privateKeyFile);
             const char *keyfile = keys.privateKeyFile.c_str();
-            ssl_ask_password(sslContext, keyfile);
+            ssl_ask_password(ctx.get(), keyfile);
 
-            if (!SSL_CTX_use_PrivateKey_file(sslContext, keyfile, SSL_FILETYPE_PEM)) {
-                const int ssl_error = ERR_get_error();
+            if (!SSL_CTX_use_PrivateKey_file(ctx.get(), keyfile, SSL_FILETYPE_PEM)) {
+                const auto ssl_error = ERR_get_error();
                 fatalf("Failed to acquire SSL private key '%s': %s\n",
-                       keyfile, ERR_error_string(ssl_error, NULL));
+                       keyfile, Security::ErrorString(ssl_error));
             }
 
             debugs(83, 5, "Comparing private and public SSL keys.");
 
-            if (!SSL_CTX_check_private_key(sslContext)) {
-                const int ssl_error = ERR_get_error();
+            if (!SSL_CTX_check_private_key(ctx.get())) {
+                const auto ssl_error = ERR_get_error();
                 fatalf("SSL private key '%s' does not match public key '%s': %s\n",
-                       certfile, keyfile, ERR_error_string(ssl_error, NULL));
+                       certfile, keyfile, Security::ErrorString(ssl_error));
             }
         }
     }
 
-    debugs(83, 9, "Setting RSA key generation callback.");
-    SSL_CTX_set_tmp_rsa_callback(sslContext, ssl_temp_rsa_cb);
-
-    if (fl & SSL_FLAG_DONT_VERIFY_PEER) {
-        debugs(83, 2, "NOTICE: Peer certificates are not verified for validity!");
-        SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL);
-    } else {
-        debugs(83, 9, "Setting certificate verification callback.");
-        SSL_CTX_set_verify(sslContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb);
-    }
-
-    return sslContext;
-}
-
-/// \ingroup ServerProtocolSSLInternal
-int
-ssl_read_method(int fd, char *buf, int len)
-{
-    auto ssl = fd_table[fd].ssl.get();
-
-#if DONT_DO_THIS
-
-    if (!SSL_is_init_finished(ssl)) {
-        errno = ENOTCONN;
-        return -1;
-    }
-
-#endif
-
-    int i = SSL_read(ssl, buf, len);
-
-    if (i > 0 && SSL_pending(ssl) > 0) {
-        debugs(83, 2, "SSL FD " << fd << " is pending");
-        fd_table[fd].flags.read_pending = true;
-    } else
-        fd_table[fd].flags.read_pending = false;
-
-    return i;
-}
-
-/// \ingroup ServerProtocolSSLInternal
-int
-ssl_write_method(int fd, const char *buf, int len)
-{
-    auto ssl = fd_table[fd].ssl.get();
-    if (!SSL_is_init_finished(ssl)) {
-        errno = ENOTCONN;
-        return -1;
-    }
+    MaybeSetupRsaCallback(ctx);
 
-    int i = SSL_write(ssl, buf, len);
-    return i;
-}
+    Ssl::ConfigurePeerVerification(ctx, fl);
 
-void
-ssl_shutdown_method(SSL *ssl)
-{
-    SSL_shutdown(ssl);
+    return true;
 }
 
 /// \ingroup ServerProtocolSSLInternal
@@ -803,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)
@@ -853,131 +877,122 @@ 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;
+    assert(ssl);
 
-    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);
-
-    str[len] = '\0';
-
-    X509_free(cert);
+    if (const auto cert = SSL_get_peer_certificate(ssl))
+        return Ssl::GetX509PEM(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;
-
-    safe_free(str);
-
-    if (!ssl)
-        return NULL;
+    assert(ssl);
 
-    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::ContextPtr
-Ssl::createSSLContext(Security::CertPointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port)
+Security::ContextPointer
+Ssl::createSSLContext(Security::CertPointer & x509, Security::PrivateKeyPointer & pkey, Security::ServerOptions &options)
 {
-#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
-    Security::ContextPointer sslContext(SSL_CTX_new(TLS_server_method()));
-#else
-    Security::ContextPointer sslContext(SSL_CTX_new(SSLv23_server_method()));
-#endif
+    Security::ContextPointer ctx(options.createBlankContext());
 
-    if (!SSL_CTX_use_certificate(sslContext.get(), x509.get()))
-        return NULL;
+    if (!SSL_CTX_use_certificate(ctx.get(), x509.get()))
+        return Security::ContextPointer();
 
-    if (!SSL_CTX_use_PrivateKey(sslContext.get(), pkey.get()))
-        return NULL;
+    if (!SSL_CTX_use_PrivateKey(ctx.get(), pkey.get()))
+        return Security::ContextPointer();
 
-    if (!configureSslContext(sslContext.get(), port))
-        return NULL;
+    if (!options.updateContextConfig(ctx))
+        return Security::ContextPointer();
 
-    return sslContext.release();
+    return ctx;
 }
 
-Security::ContextPtr
-Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port)
+Security::ContextPointer
+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 nullptr;
+        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::ContextPtr
-Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port)
+Security::ContextPointer
+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 nullptr;
+        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, Security::ServerOptions &options)
+{
+    assert(ctx);
+    // Add signing certificate to the certificates chain
+    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 auto ssl_error = ERR_get_error();
+        debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << Security::ErrorString(ssl_error));
+    }
+
+    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.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;
 
@@ -1000,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;
 
@@ -1016,75 +1031,58 @@ Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::Po
     return true;
 }
 
-bool Ssl::verifySslCertificate(Security::ContextPtr sslContext, CertificateProperties const &properties)
+bool
+Ssl::verifySslCertificate(const Security::ContextPointer &ctx, CertificateProperties const &properties)
 {
+#if HAVE_SSL_CTX_GET0_CERTIFICATE
+    X509 * cert = SSL_CTX_get0_certificate(ctx.get());
+#elif SQUID_USE_SSLGETCERTIFICATE_HACK
     // SSL_get_certificate is buggy in openssl versions 1.0.1d and 1.0.1e
-    // Try to retrieve certificate directly from Security::ContextPtr object
-#if SQUID_USE_SSLGETCERTIFICATE_HACK
-    X509 ***pCert = (X509 ***)sslContext->cert;
+    // Try to retrieve certificate directly from Security::ContextPointer object
+    X509 ***pCert = (X509 ***)ctx->cert;
     X509 * cert = pCert && *pCert ? **pCert : NULL;
 #elif SQUID_SSLGETCERTIFICATE_BUGGY
     X509 * cert = NULL;
     assert(0);
 #else
     // Temporary ssl for getting X509 certificate from SSL_CTX.
-    Security::SessionPointer ssl(SSL_new(sslContext));
+    Security::SessionPointer ssl(Security::NewSessionObject(ctx));
     X509 * cert = SSL_get_certificate(ssl.get());
 #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): " <<
-               ERR_error_string(ssl_error, NULL) << "\n");
-        return false;
+               Security::ErrorString(ssl_error) << "\n");
     }
-    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::ContextPtr sslContext, 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(sslContext, cert)) {
-            // increase the certificate lock
-            CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509);
-        } else {
-            const int ssl_error = ERR_get_error();
-            debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL));
-        }
-    }
-}
-
-static const char *
-hasAuthorityInfoAccessCaIssuers(X509 *cert)
+const char *
+Ssl::findIssuerUri(X509 *cert)
 {
     AUTHORITY_INFO_ACCESS *info;
     if (!cert)
         return nullptr;
-    info = (AUTHORITY_INFO_ACCESS *)X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
+    info = static_cast<AUTHORITY_INFO_ACCESS *>(X509_get_ext_d2i(cert, NID_info_access, NULL, NULL));
     if (!info)
         return nullptr;
 
@@ -1095,7 +1093,11 @@ hasAuthorityInfoAccessCaIssuers(X509 *cert)
         ACCESS_DESCRIPTION *ad = sk_ACCESS_DESCRIPTION_value(info, i);
         if (OBJ_obj2nid(ad->method) == NID_ad_ca_issuers) {
             if (ad->location->type == GEN_URI) {
-                xstrncpy(uri, (char *)ASN1_STRING_data(ad->location->d.uniformResourceIdentifier), sizeof(uri));
+                xstrncpy(uri,
+                         reinterpret_cast<const char *>(
+                             ASN1_STRING_get0_data(ad->location->d.uniformResourceIdentifier)
+                         ),
+                         sizeof(uri));
             }
             break;
         }
@@ -1124,9 +1126,10 @@ Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
     return true;
 }
 
-/// quickly find a certificate with a given issuer in Ssl::CertsIndexedList.
+/// quickly find the issuer certificate of a certificate cert in the
+/// Ssl::CertsIndexedList list
 static X509 *
-findCertByIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
+findCertIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
 {
     static char buffer[2048];
 
@@ -1138,137 +1141,204 @@ findCertByIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
     const auto ret = list.equal_range(SBuf(buffer));
     for (Ssl::CertsIndexedList::iterator it = ret.first; it != ret.second; ++it) {
         X509 *issuer = it->second;
-        if (X509_check_issued(cert, issuer)) {
+        if (X509_check_issued(issuer, cert) == X509_V_OK) {
             return issuer;
         }
     }
     return NULL;
 }
 
-/// slowly find a certificate with a given issuer using linear search
+/// slowly find the issuer certificate of a given cert using linear search
 static X509 *
-findCertByIssuerSlowly(STACK_OF(X509) *sk, X509 *cert)
+sk_x509_findIssuer(const STACK_OF(X509) *sk, X509 *cert)
 {
     if (!sk)
-        return NULL;
+        return nullptr;
 
-    const int skItemsNum = sk_X509_num(sk);
-    for (int i = 0; i < skItemsNum; ++i) {
-        X509 *issuer = sk_X509_value(sk, i);
+    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 NULL;
+    return nullptr;
 }
 
-const char *
-Ssl::uriOfIssuerIfMissing(X509 *cert,  Ssl::X509_STACK_Pointer 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.get())
+    if (!connContext)
         return nullptr;
 
-    if (!findCertByIssuerSlowly(serverCertificates.get(), cert)) {
-        //if issuer is missing
-        if (!findCertByIssuerFast(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.
-                // Check to see if this is required.
-                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, Ssl::X509_STACK_Pointer const &serverCertificates)
+Security::CertPointer
+Ssl::findIssuerCertificate(X509 *cert, const STACK_OF(X509) *serverCertificates, const Security::ContextPointer &context)
 {
-    if (!serverCertificates.get())
-        return;
+    Must(cert);
 
-    for (int i = 0; i < sk_X509_num(serverCertificates.get()); ++i) {
-        X509 *cert = sk_X509_value(serverCertificates.get(), i);
-        if (const char *issuerUri = uriOfIssuerIfMissing(cert, 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);
+    }
+
+    // check untrusted certificates
+    if (const auto issuer = findCertIssuerFast(SquidUntrustedCerts, cert)) {
+        X509_up_ref(issuer);
+        return Security::CertPointer(issuer);
     }
+
+    // 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);
 }
 
-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");
+bool
+Ssl::missingChainCertificatesUrls(std::queue<SBuf> &URIs, const STACK_OF(X509) &serverCertificates, const Security::ContextPointer &context)
+{
+    for (int i = 0; i < sk_X509_num(&serverCertificates); ++i) {
+        const auto cert = sk_X509_value(&serverCertificates, i);
+
+        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");
         }
     }
-    sk_X509_push(untrustedStack, cert);
+
+    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");
 
-    int depth = ctx->param->depth;
-    X509 *current = ctx->cert;
+    const X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
+    int depth = X509_VERIFY_PARAM_get_depth(param);
+    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)) {
+        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 = findCertByIssuerSlowly(untrustedCerts, current);
-        if (!issuer) {
-            if ((issuer = findCertByIssuerFast(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 = (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
-    STACK_OF(X509) *oldUntrusted = ctx->untrusted;
-    STACK_OF(X509) *sk = sk_X509_dup(oldUntrusted); // oldUntrusted is always not NULL
-
-    for (int i = 0; i < sk_X509_num(sslUntrustedStack); ++i) {
-        X509 *cert = sk_X509_value(sslUntrustedStack, i);
-        sk_X509_push(sk, cert);
+    STACK_OF(X509) *oldUntrusted = X509_STORE_CTX_get0_untrusted(ctx);
+    // 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());
+
+    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);
-    X509_STORE_CTX_set_chain(ctx, oldUntrusted); // Set back the old untrusted list
-    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)
 {
@@ -1292,64 +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_internal()));
-    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.reset(readSslPrivateKey(keyFilename, cb));
-    cert.reset(readSslX509CertificatesChain(certFilename, chain.get()));
-    if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) {
-        pkey.reset(NULL);
-        cert.reset(NULL);
-    }
-}
-
-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;
@@ -1372,179 +1385,115 @@ bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, EVP_PKEY_P
     return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties);
 }
 
-SSL *
-SslCreate(Security::ContextPtr sslContext, const int fd, Ssl::Bio::Type type, const char *squidCtx)
+void Ssl::InRamCertificateDbKey(const Ssl::CertificateProperties &certProperties, SBuf &key)
 {
-    if (fd < 0) {
-        debugs(83, DBG_IMPORTANT, "Gone connection");
-        return NULL;
-    }
-
-    const char *errAction = NULL;
-    int errCode = 0;
-    if (auto ssl = SSL_new(sslContext)) {
-        // without BIO, we would call SSL_set_fd(ssl, fd) instead
-        if (BIO *bio = Ssl::Bio::Create(fd, type)) {
-            Ssl::Bio::Link(ssl, bio); // cannot fail
-
-            fd_table[fd].ssl.reset(ssl);
-            fd_table[fd].read_method = &ssl_read_method;
-            fd_table[fd].write_method = &ssl_write_method;
-            fd_note(fd, squidCtx);
-            return ssl;
+    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);
         }
-        errCode = ERR_get_error();
-        errAction = "failed to initialize I/O";
-        SSL_free(ssl);
-    } else {
-        errCode = ERR_get_error();
-        errAction = "failed to allocate handle";
     }
 
-    debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
-           ": " << ERR_error_string(errCode, NULL));
-    return NULL;
-}
-
-SSL *
-Ssl::CreateClient(Security::ContextPtr sslContext, const int fd, const char *squidCtx)
-{
-    return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_SERVER, squidCtx);
-}
-
-SSL *
-Ssl::CreateServer(Security::ContextPtr sslContext, const int fd, const char *squidCtx)
-{
-    return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_CLIENT, squidCtx);
-}
-
-Ssl::CertError::CertError(ssl_error_t anErr, X509 *aCert, int aDepth): code(anErr), depth(aDepth)
-{
-    cert.resetAndLock(aCert);
-}
-
-Ssl::CertError::CertError(CertError const &err): code(err.code), depth(err.depth)
-{
-    cert.resetAndLock(err.cert.get());
-}
-
-Ssl::CertError &
-Ssl::CertError::operator = (const CertError &old)
-{
-    code = old.code;
-    cert.resetAndLock(old.cert.get());
-    return *this;
-}
-
-bool
-Ssl::CertError::operator == (const CertError &ce) const
-{
-    return code == ce.code && cert.get() == ce.cert.get();
+    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());
+    }
 }
 
-bool
-Ssl::CertError::operator != (const CertError &ce) const
+static int
+bio_sbuf_create(BIO* bio)
 {
-    return code != ce.code || cert.get() != ce.cert.get();
+    BIO_set_init(bio, 0);
+    BIO_set_data(bio, NULL);
+    return 1;
 }
 
 static int
-store_session_cb(SSL *ssl, SSL_SESSION *session)
+bio_sbuf_destroy(BIO* bio)
 {
-    if (!Ssl::SessionCache)
+    if (!bio)
         return 0;
-
-    debugs(83, 5, "Request to store SSL Session ");
-
-    SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
-
-    unsigned char *id = session->session_id;
-    unsigned int idlen = session->session_id_length;
-    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);
-        }
-        Ssl::SessionCache->closeForWriting(pos);
-        debugs(83, 5, "wrote an ssl session entry of size " << lenRequired << " at pos " << pos);
-    }
-    return 0;
+    return 1;
 }
 
-static void
-remove_session_cb(SSL_CTX *, SSL_SESSION *sessionID)
+int
+bio_sbuf_write(BIO* bio, const char* data, int len)
 {
-    if (!Ssl::SessionCache)
-        return ;
-
-    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);
+    SBuf *buf = static_cast<SBuf *>(BIO_get_data(bio));
+    // TODO: Convert exceptions into BIO errors
+    buf->append(data, len);
+    return len;
 }
 
-static SSL_SESSION *
-get_session_cb(SSL *, unsigned char *sessionID, int len, int *copy)
-{
-    if (!Ssl::SessionCache)
-        return NULL;
-
-    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_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;
+}
+
+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;
     }
-
-    if (!session)
-        debugs(83, 5, "Failed to retrieved from cache\n");
-
-    // 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;
 }
 
-void
-Ssl::SetSessionCallbacks(Security::ContextPtr ctx)
+BIO *Ssl::BIO_new_SBuf(SBuf *buf)
 {
-    if (Ssl::SessionCache) {
-        SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL);
-        SSL_CTX_sess_set_new_cb(ctx, store_session_cb);
-        SSL_CTX_sess_set_remove_cb(ctx, remove_session_cb);
-        SSL_CTX_sess_set_get_cb(ctx, 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 */