/*
 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

/* DEBUG: section 83    SSL accelerator support */

#include "squid.h"

/* MS Visual Studio Projects are monolithic, so we need the following
 * #if to exclude the SSL code from compile process when not needed.
 */
#if USE_OPENSSL

#include "acl/FilledChecklist.h"
#include "anyp/Host.h"
#include "anyp/PortCfg.h"
#include "anyp/Uri.h"
#include "fatal.h"
#include "fd.h"
#include "fde.h"
#include "globals.h"
#include "ip/Address.h"
#include "ipc/MemMap.h"
#include "security/CertError.h"
#include "security/Certificate.h"
#include "security/ErrorDetail.h"
#include "security/Session.h"
#include "SquidConfig.h"
#include "ssl/bio.h"
#include "ssl/Config.h"
#include "ssl/ErrorDetail.h"
#include "ssl/gadgets.h"
#include "ssl/support.h"

#include <cerrno>

// TODO: Move ssl_ex_index_* global variables from global.cc here.
static int ssl_ex_index_verify_callback_parameters = -1;

static Ssl::CertsIndexedList SquidUntrustedCerts;

const EVP_MD *Ssl::DefaultSignHash = nullptr;

std::vector<const char *> Ssl::BumpModeStr = {
    "none",
    "client-first",
    "server-first",
    "peek",
    "stare",
    "bump",
    "splice",
    "terminate"
    /*,"err"*/
};

namespace Ssl {

/// GeneralNameMatcher for matching a single AnyP::Host given at construction time
class OneNameMatcher: public GeneralNameMatcher
{
public:
    explicit OneNameMatcher(const AnyP::Host &needle): needle_(needle) {}

protected:
    /* GeneralNameMatcher API */
    bool matchDomainName(const Dns::DomainName &) const override;
    bool matchIp(const Ip::Address &) const override;

    AnyP::Host needle_; ///< a name we are looking for
};

} // namespace Ssl

bool
Ssl::GeneralNameMatcher::match(const GeneralName &name) const
{
    if (const auto domain = name.domainName())
        return matchDomainName(*domain);
    if (const auto ip = name.ip())
        return matchIp(*ip);
    Assure(!"unreachable code: the above `if` statements must cover all name variants");
    return false;
}

bool
Ssl::OneNameMatcher::matchDomainName(const Dns::DomainName &rawName) const {
    // TODO: Add debugs() stream manipulator to safely (i.e. without breaking
    // cache.log message framing) dump raw input that may contain new lines. Use
    // here and in similar contexts where we report such raw input.
    debugs(83, 5, "needle=" << needle_ << " domain=" << rawName);
    if (needle_.ip()) {
        // for example, a 127.0.0.1 IP needle does not match DNS:127.0.0.1 SAN
        debugs(83, 7, "needle is an IP; mismatch");
        return false;
    }

    Assure(needle_.domainName());
    auto domainNeedle = *needle_.domainName();

    auto name = rawName;
    if (name.length() > 0 && name[0] == '*')
        name.consume(1);

    return ::matchDomainName(domainNeedle.c_str(), name.c_str(), mdnRejectSubsubDomains) == 0;
}

bool
Ssl::OneNameMatcher::matchIp(const Ip::Address &ip) const {
    debugs(83, 5, "needle=" << needle_ << " ip=" << ip);
    if (const auto needleIp = needle_.ip())
        return (*needleIp == ip);
    debugs(83, 7, "needle is not an IP; mismatch");
    return false;
}

/**
 \defgroup ServerProtocolSSLInternal Server-Side SSL Internals
 \ingroup ServerProtocolSSLAPI
 */

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);
    in = popen(cmdline, "r");

    if (fgets(buf, size, in))

        len = strlen(buf);

    while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
        --len;

    buf[len] = '\0';

    pclose(in);

    return len;
}

/// \ingroup ServerProtocolSSLInternal
static void
ssl_ask_password(SSL_CTX * context, const char * prompt)
{
    if (Config.Program.ssl_password) {
        SSL_CTX_set_default_passwd_cb(context, Ssl::AskPasswordCb);
        SSL_CTX_set_default_passwd_cb_userdata(context, (void *)prompt);
    }
}

#if HAVE_LIBSSL_SSL_CTX_SET_TMP_RSA_CALLBACK
static RSA *
ssl_temp_rsa_cb(SSL *, int, int keylen)
{
    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, "ERROR: 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_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;
        break;

    case 1024:

        if (!rsa_1024) {
            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;
        break;

    default:
        debugs(83, DBG_IMPORTANT, "ERROR: ssl_temp_rsa_cb: Unexpected key length " << keylen);
        return nullptr;
    }

    if (rsa == NULL) {
        debugs(83, DBG_IMPORTANT, "ERROR: ssl_temp_rsa_cb: Failed to generate key " << keylen);
        return nullptr;
    }

    if (newkey) {
        if (Debug::Enabled(83, 5))
            PEM_write_RSAPrivateKey(DebugStream(), rsa, nullptr, nullptr, 0, nullptr, nullptr);

        debugs(83, DBG_IMPORTANT, "Generated ephemeral RSA key of length " << 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);
#else
    (void)ctx;
#endif
}

int Ssl::asn1timeToString(ASN1_TIME *tm, char *buf, int len)
{
    BIO *bio;
    int write = 0;
    bio = BIO_new(BIO_s_mem());
    if (bio) {
        if (ASN1_TIME_print(bio, tm))
            write = BIO_read(bio, buf, len-1);
        BIO_free(bio);
    }
    buf[write]='\0';
    return write;
}

static std::optional<AnyP::Host>
ParseSubjectAltName(const GENERAL_NAME &san)
{
    switch(san.type) {
    case GEN_DNS: {
        Assure(san.d.dNSName);
        // GEN_DNS is an IA5STRING. IA5STRING is a subset of ASCII that does not
        // need to be converted to UTF-8 (or some such) before we parse it.
        const auto buffer = Ssl::AsnToSBuf(*san.d.dNSName);
        return AnyP::Host::ParseWildDomainName(buffer);
    }

    case GEN_IPADD: {
        // san.d.iPAddress is OpenSSL ASN1_OCTET_STRING
        Assure(san.d.iPAddress);

        // RFC 5280 section 4.2.1.6 signals IPv4/IPv6 address family using data length

        if (san.d.iPAddress->length == 4) {
            struct in_addr addr;
            static_assert(sizeof(addr.s_addr) == 4);
            memcpy(&addr.s_addr, san.d.iPAddress->data, sizeof(addr.s_addr));
            const Ip::Address ip(addr);
            return AnyP::Host::ParseIp(ip);
        }

        if (san.d.iPAddress->length == 16) {
            struct in6_addr addr;
            static_assert(sizeof(addr.s6_addr) == 16);
            memcpy(&addr.s6_addr, san.d.iPAddress->data, sizeof(addr.s6_addr));
            const Ip::Address ip(addr);
            return AnyP::Host::ParseIp(ip);
        }

        debugs(83, 3, "unexpected length of an IP address SAN: " << san.d.iPAddress->length);
        return std::nullopt;
    }

    default:
        debugs(83, 3, "unsupported SAN kind: " << san.type);
        return std::nullopt;
    }
}

bool
Ssl::HasMatchingSubjectName(X509 &cert, const GeneralNameMatcher &matcher)
{
    const auto name = X509_get_subject_name(&cert);
    for (int i = X509_NAME_get_index_by_NID(name, NID_commonName, -1); i >= 0; i = X509_NAME_get_index_by_NID(name, NID_commonName, i)) {
        debugs(83, 7, "checking CN at " << i);
        if (const auto cn = ParseCommonNameAt(*name, i)) {
            if (matcher.match(*cn))
                return true;
        }
    }

    const Ssl::GENERAL_NAME_STACK_Pointer sans(static_cast<STACK_OF(GENERAL_NAME)*>(
                X509_get_ext_d2i(&cert, NID_subject_alt_name, nullptr, nullptr)));
    if (sans) {
        const auto sanCount = sk_GENERAL_NAME_num(sans.get());
        for (int i = 0; i < sanCount; ++i) {
            debugs(83, 7, "checking SAN at " << i);
            const auto rawSan = sk_GENERAL_NAME_value(sans.get(), i);
            Assure(rawSan);
            if (const auto san = ParseSubjectAltName(*rawSan)) {
                if (matcher.match(*san))
                    return true;
            }
        }
    }

    debugs(83, 7, "no matches");
    return false;
}

bool
Ssl::HasSubjectName(X509 &cert, const AnyP::Host &host)
{
    return HasMatchingSubjectName(cert, OneNameMatcher(host));
}

/// 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
    Security::ErrorCode error_no = ok ? SSL_ERROR_NONE : X509_STORE_CTX_get_error(ctx);

    SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
    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);
    Security::CertPointer peer_cert;
    peer_cert.resetAndLock(X509_STORE_CTX_get0_cert(ctx));

    // detect infinite loops
    uint32_t *validationCounter = static_cast<uint32_t *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_validation_counter));
    if (!validationCounter) {
        validationCounter = new uint32_t(1);
        SSL_set_ex_data(ssl, ssl_ex_index_ssl_validation_counter, validationCounter);
    } else {
        // overflows allowed if SQUID_CERT_VALIDATION_ITERATION_MAX >= UINT32_MAX
        (*validationCounter)++;
    }

    if ((*validationCounter) >= SQUID_CERT_VALIDATION_ITERATION_MAX) {
        ok = 0; // or the validation loop will never stop
        error_no = SQUID_X509_V_ERR_INFINITE_VALIDATION;
        debugs(83, 2, "SQUID_X509_V_ERR_INFINITE_VALIDATION: " <<
               *validationCounter << " iterations while checking " << *peer_cert);
    }

    if (ok) {
        debugs(83, 5, "SSL Certificate signature OK: " << *peer_cert);

        // Check for domain mismatch only if the current certificate is the peer certificate.
        if (!dont_verify_domain && server && peer_cert.get() == X509_STORE_CTX_get_current_cert(ctx)) {
            // XXX: This code does not know where the server name came from. The
            // name may be valid but not compatible with requirements assumed or
            // enforced by the AnyP::Host::ParseSimpleDomainName() call below.
            // TODO: Store AnyP::Host (or equivalent) in ssl_ex_index_server.
            if (const auto host = Ssl::ParseAsSimpleDomainNameOrIp(*server)) {
                if (Ssl::HasSubjectName(*peer_cert, *host)) {
                    debugs(83, 5, "certificate subject matches " << *host);
                } else {
                    debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate " << *peer_cert << " does not match domainname " << *host);
                    ok = 0;
                    error_no = SQUID_X509_V_ERR_DOMAIN_MISMATCH;
                }
            } else {
                debugs(83, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Cannot check whether certificate " << *peer_cert << " subject matches malformed domainname " << *server);
                ok = 0;
                error_no = SQUID_X509_V_ERR_DOMAIN_MISMATCH;
            }
        }
    }

    if (ok && peeked_cert) {
        // Check whether the already peeked certificate matches the new one.
        if (X509_cmp(peer_cert.get(), peeked_cert) != 0) {
            debugs(83, 2, "SQUID_X509_V_ERR_CERT_CHANGE: Certificate " << *peer_cert << " 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) {
        Security::CertPointer broken_cert;
        broken_cert.resetAndLock(X509_STORE_CTX_get_current_cert(ctx));
        if (!broken_cert)
            broken_cert = peer_cert;

        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) {
            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 " << *peer_cert);
                delete errs;
                errs = nullptr;
            }
        } else // remember another error number
            errs->push_back_unique(Security::CertError(error_no, broken_cert, depth));

        if (const auto description = Ssl::GetErrorDescr(error_no))
            debugs(83, 5, *description << ": " << *peer_cert);
        else
            debugs(83, DBG_IMPORTANT, "ERROR: SSL unknown certificate error " << error_no << " in " << *peer_cert);

        // Check if the certificate error can be bypassed.
        // Infinity validation loop errors can not bypassed.
        if (error_no != SQUID_X509_V_ERR_INFINITE_VALIDATION) {
            if (check) {
                ACLFilledChecklist *filledCheck = Filled(check);
                const auto savedErrors = filledCheck->sslErrors;
                const auto sslErrors = std::make_unique<Security::CertErrors>(Security::CertError(error_no, broken_cert));
                filledCheck->sslErrors = sslErrors.get();
                filledCheck->serverCert = peer_cert;
                if (check->fastCheck().allowed()) {
                    debugs(83, 3, "bypassing SSL error " << error_no << " in " << *peer_cert);
                    ok = 1;
                } else {
                    debugs(83, 5, "confirming SSL error " << error_no);
                }
                filledCheck->sslErrors = savedErrors;
                filledCheck->serverCert.reset();
            }
            // If the certificate validator is used then we need to allow all errors and
            // pass them to certificate validator for more processing
            else if (Ssl::TheConfig.ssl_crt_validator) {
                ok = 1;
            }
        }
    }

    if (Ssl::TheConfig.ssl_crt_validator) {
        // Check if we have stored certificates chain. Store if not.
        if (!SSL_get_ex_data(ssl, ssl_ex_index_ssl_cert_chain)) {
            STACK_OF(X509) *certStack = X509_STORE_CTX_get1_chain(ctx);
            if (certStack && !SSL_set_ex_data(ssl, ssl_ex_index_ssl_cert_chain, certStack))
                sk_X509_pop_free(certStack, X509_free);
        }
    }

    if (!ok && !SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail) ) {

        // Find the broken certificate. It may be intermediate.
        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 (auto *last_used_cert = X509_STORE_CTX_get_current_cert(ctx))
                broken_cert.resetAndLock(last_used_cert);
        }

        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 " << *peer_cert << " 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, "WARNING: UPGRADE: 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 OPENSSL_VERSION_MAJOR >= 3
static int
ssl_dupAclChecklist(CRYPTO_EX_DATA *, const CRYPTO_EX_DATA *, void **,
                    int, long, void *)
#elif 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.
    assert(false);
    return 0;
}

// "free" function for SSL_get_ex_new_index("cert_err_check")
static void
ssl_freeAclChecklist(void *, void *ptr, CRYPTO_EX_DATA *,
                     int, long, void *)
{
    delete static_cast<ACLChecklist *>(ptr); // may be NULL
}

// "free" function for SSL_get_ex_new_index("ssl_error_detail")
static void
ssl_free_ErrorDetail(void *, void *ptr, CRYPTO_EX_DATA *,
                     int, long, void *)
{
    const auto errDetail = static_cast<Security::ErrorDetail::Pointer*>(ptr);
    delete errDetail;
}

static void
ssl_free_SslErrors(void *, void *ptr, CRYPTO_EX_DATA *,
                   int, long, void *)
{
    Security::CertErrors *errs = static_cast <Security::CertErrors*>(ptr);
    delete errs;
}

// "free" function for SSL_get_ex_new_index("ssl_ex_index_ssl_validation_counter")
static void
ssl_free_int(void *, void *ptr, CRYPTO_EX_DATA *,
             int, long, void *)
{
    uint32_t *counter = static_cast <uint32_t *>(ptr);
    delete counter;
}

/// \ingroup ServerProtocolSSLInternal
/// Callback handler function to release STACK_OF(X509) "ex" data stored
/// in an SSL object.
static void
ssl_free_CertChain(void *, void *ptr, CRYPTO_EX_DATA *,
                   int, long, void *)
{
    STACK_OF(X509) *certsChain = static_cast <STACK_OF(X509) *>(ptr);
    sk_X509_pop_free(certsChain,X509_free);
}

// "free" function for X509 certificates
static void
ssl_free_X509(void *, void *ptr, CRYPTO_EX_DATA *,
              int, long, void *)
{
    X509  *cert = static_cast <X509 *>(ptr);
    X509_free(cert);
}

// "free" function for SBuf
static void
ssl_free_SBuf(void *, void *ptr, CRYPTO_EX_DATA *,
              int, long, void *)
{
    SBuf  *buf = static_cast <SBuf *>(ptr);
    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)
{
    static bool initialized = false;
    if (initialized)
        return;
    initialized = true;

    SQUID_OPENSSL_init_ssl();

    if (::Config.SSL.ssl_engine) {
#if OPENSSL_VERSION_MAJOR < 3
        debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: Support for ssl_engine is deprecated " <<
               "in Squids built with OpenSSL 1.x (like this Squid). " <<
               "It is removed in Squids built with OpenSSL 3.0 or newer.");
#if !defined(OPENSSL_NO_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 auto ssl_error = ERR_get_error();
            fatalf("Failed to initialise SSL engine: %s\n", Security::ErrorString(ssl_error));
        }
#else /* OPENSSL_NO_ENGINE */
        throw TextException("Cannot use ssl_engine in Squid built with OpenSSL configured to disable SSL engine support", Here());
#endif

#else /* OPENSSL_VERSION_MAJOR */
        throw TextException("Cannot use ssl_engine in Squid built with OpenSSL 3.0 or newer", Here());
#endif
    }

    const char *defName = ::Config.SSL.certSignHash ? ::Config.SSL.certSignHash : SQUID_SSL_SIGN_HASH_IF_NONE;
    Ssl::DefaultSignHash = EVP_get_digestbyname(defName);
    if (!Ssl::DefaultSignHash)
        fatalf("Sign hash '%s' is not supported\n", defName);

    ssl_ex_index_server = SSL_get_ex_new_index(0, (void *) "server", nullptr, nullptr, ssl_free_SBuf);
    ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", nullptr, nullptr, nullptr);
    ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", nullptr, &ssl_dupAclChecklist, &ssl_freeAclChecklist);
    ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", nullptr, nullptr, &ssl_free_ErrorDetail);
    ssl_ex_index_ssl_peeked_cert  = SSL_get_ex_new_index(0, (void *) "ssl_peeked_cert", nullptr, nullptr, &ssl_free_X509);
    ssl_ex_index_ssl_errors =  SSL_get_ex_new_index(0, (void *) "ssl_errors", nullptr, nullptr, &ssl_free_SslErrors);
    ssl_ex_index_ssl_cert_chain = SSL_get_ex_new_index(0, (void *) "ssl_cert_chain", nullptr, nullptr, &ssl_free_CertChain);
    ssl_ex_index_ssl_validation_counter = SSL_get_ex_new_index(0, (void *) "ssl_validation_counter", nullptr, nullptr, &ssl_free_int);
    ssl_ex_index_verify_callback_parameters = SSL_get_ex_new_index(0, (void *) "verify_callback_parameters", nullptr, nullptr, &ssl_free_VerifyCallbackParameters);
}

bool
Ssl::InitServerContext(Security::ContextPointer &ctx, AnyP::PortCfg &)
{
    if (!ctx)
        return false;

    return true;
}

bool
Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, Security::ParsedPortFlags fl)
{
    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(ctx.get(), cipher)) {
            const auto ssl_error = ERR_get_error();
            fatalf("Failed to set SSL cipher suite '%s': %s\n",
                   cipher, Security::ErrorString(ssl_error));
        }
    }

    if (!peer.certs.empty()) {
        // TODO: support loading multiple cert/key pairs
        auto &keys = peer.certs.front();
        if (!keys.certFile.isEmpty()) {
            debugs(83, 2, "loading client certificate from " << keys.certFile);

            const char *certfile = keys.certFile.c_str();
            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, Security::ErrorString(ssl_error));
            }

            debugs(83, 2, "loading private key from " << keys.privateKeyFile);
            const char *keyfile = keys.privateKeyFile.c_str();
            ssl_ask_password(ctx.get(), keyfile);

            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, Security::ErrorString(ssl_error));
            }

            debugs(83, 5, "Comparing private and public SSL keys.");

            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, Security::ErrorString(ssl_error));
            }
        }
    }

    MaybeSetupRsaCallback(ctx);

    Ssl::ConfigurePeerVerification(ctx, fl);

    return true;
}

/// \ingroup ServerProtocolSSLInternal
static const char *
ssl_get_attribute(X509_NAME * name, const char *attribute_name)
{
    static char buffer[1024];
    buffer[0] = '\0';

    if (strcmp(attribute_name, "DN") == 0) {
        X509_NAME_oneline(name, buffer, sizeof(buffer));
    } else {
        int nid = OBJ_txt2nid(const_cast<char *>(attribute_name));
        if (nid == 0) {
            debugs(83, DBG_IMPORTANT, "WARNING: Unknown SSL attribute name '" << attribute_name << "'");
            return nullptr;
        }
        X509_NAME_get_text_by_NID(name, nid, buffer, sizeof(buffer));
    }

    return *buffer ? buffer : nullptr;
}

/// \ingroup ServerProtocolSSLInternal
const char *
Ssl::GetX509UserAttribute(X509 * cert, const char *attribute_name)
{
    X509_NAME *name;
    const char *ret;

    if (!cert)
        return nullptr;

    name = X509_get_subject_name(cert);

    ret = ssl_get_attribute(name, attribute_name);

    return ret;
}

const char *
Ssl::GetX509Fingerprint(X509 * cert, const char *)
{
    static char buf[1024];
    if (!cert)
        return nullptr;

    unsigned int n;
    unsigned char md[EVP_MAX_MD_SIZE];
    if (!X509_digest(cert, EVP_sha1(), md, &n))
        return nullptr;

    assert(3 * n + 1 < sizeof(buf));

    char *s = buf;
    for (unsigned int i=0; i < n; ++i, s += 3) {
        const char term = (i + 1 < n) ? ':' : '\0';
        snprintf(s, 4, "%02X%c", md[i], term);
    }

    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)
{

    X509_NAME *name;
    const char *ret;

    if (!cert)
        return nullptr;

    name = X509_get_issuer_name(cert);

    ret = ssl_get_attribute(name, attribute_name);

    return ret;
}

const char *sslGetUserAttribute(SSL *ssl, const char *attribute_name)
{
    if (!ssl)
        return nullptr;

    X509 *cert = SSL_get_peer_certificate(ssl);

    const char *attr = Ssl::GetX509UserAttribute(cert, attribute_name);

    X509_free(cert);
    return attr;
}

const char *sslGetCAAttribute(SSL *ssl, const char *attribute_name)
{
    if (!ssl)
        return nullptr;

    X509 *cert = SSL_get_peer_certificate(ssl);

    const char *attr = Ssl::GetX509CAAttribute(cert, attribute_name);

    X509_free(cert);
    return attr;
}

const char *
sslGetUserEmail(SSL * ssl)
{
    return sslGetUserAttribute(ssl, "emailAddress");
}

SBuf
sslGetUserCertificatePEM(SSL *ssl)
{
    assert(ssl);

    if (const auto cert = SSL_get_peer_certificate(ssl))
        return Ssl::GetX509PEM(cert);

    return SBuf();
}

SBuf
sslGetUserCertificateChainPEM(SSL *ssl)
{
    assert(ssl);

    auto chain = SSL_get_peer_cert_chain(ssl);

    if (!chain)
        return sslGetUserCertificatePEM(ssl);

    Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));

    for (int i = 0; i < sk_X509_num(chain); ++i) {
        X509 *cert = sk_X509_value(chain, i);
        PEM_write_bio_X509(bio.get(), cert);
    }

    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, Security::PrivateKeyPointer & pkey, Security::ServerOptions &options)
{
    Security::ContextPointer ctx(options.createBlankContext());

    if (!SSL_CTX_use_certificate(ctx.get(), x509.get()))
        return Security::ContextPointer();

    if (!SSL_CTX_use_PrivateKey(ctx.get(), pkey.get()))
        return Security::ContextPointer();

    if (!options.updateContextConfig(ctx))
        return Security::ContextPointer();

    return ctx;
}

Security::ContextPointer
Ssl::GenerateSslContextUsingPkeyAndCertFromMemory(const char * data, Security::ServerOptions &options, bool trusted)
{
    Security::CertPointer cert;
    Security::PrivateKeyPointer pkey;
    if (!readCertAndPrivateKeyFromMemory(cert, pkey, data) || !cert || !pkey)
        return Security::ContextPointer();

    Security::ContextPointer ctx(createSSLContext(cert, pkey, options));
    if (ctx && trusted)
        Ssl::chainCertificatesToSSLContext(ctx, options);
    return ctx;
}

Security::ContextPointer
Ssl::GenerateSslContext(CertificateProperties const &properties, Security::ServerOptions &options, bool trusted)
{
    Security::CertPointer cert;
    Security::PrivateKeyPointer pkey;
    if (!generateSslCertificate(cert, pkey, properties) || !cert || !pkey)
        return Security::ContextPointer();

    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 &)
{
    Security::CertPointer cert;
    Security::PrivateKeyPointer pkey;
    if (!generateSslCertificate(cert, pkey, properties))
        return false;

    if (!cert)
        return false;

    if (!pkey)
        return false;

    if (!SSL_use_certificate(ssl, cert.get()))
        return false;

    if (!SSL_use_PrivateKey(ssl, pkey.get()))
        return false;

    return true;
}

bool
Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &)
{
    Security::CertPointer cert;
    Security::PrivateKeyPointer pkey;
    if (!readCertAndPrivateKeyFromMemory(cert, pkey, data))
        return false;

    if (!cert || !pkey)
        return false;

    if (!SSL_use_certificate(ssl, cert.get()))
        return false;

    if (!SSL_use_PrivateKey(ssl, pkey.get()))
        return false;

    return true;
}

bool
Ssl::verifySslCertificate(const Security::ContextPointer &ctx, CertificateProperties const &)
{
#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::ContextPointer object
    X509 ***pCert = (X509 ***)ctx->cert;
    X509 * cert = pCert && *pCert ? **pCert : NULL;
#elif SQUID_SSLGETCERTIFICATE_BUGGY
    X509 * cert = nullptr;
    assert(0);
#else
    // Temporary ssl for getting X509 certificate from SSL_CTX.
    Security::SessionPointer ssl(Security::NewSessionObject(ctx));
    X509 * cert = SSL_get_certificate(ssl.get());
#endif
    if (!cert)
        return false;
    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);
}

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 auto ssl_error = ERR_get_error();
        debugs(83, 3,  "WARNING: unable to set TLS servername extension (SNI): " <<
               Security::ErrorString(ssl_error) << "\n");
    }
#else
    debugs(83, 7,  "no support for TLS servername extension (SNI)");
#endif
}

const char *
Ssl::findIssuerUri(X509 *cert)
{
    AUTHORITY_INFO_ACCESS *info;
    if (!cert)
        return nullptr;
    info = static_cast<AUTHORITY_INFO_ACCESS *>(X509_get_ext_d2i(cert, NID_info_access, nullptr, nullptr));
    if (!info)
        return nullptr;

    static char uri[MAX_URL];
    uri[0] = '\0';

    for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(info); i++) {
        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,
                         reinterpret_cast<const char *>(
                             ASN1_STRING_get0_data(ad->location->d.uniformResourceIdentifier)
                         ),
                         sizeof(uri));
            }
            break;
        }
    }
    AUTHORITY_INFO_ACCESS_free(info);
    return uri[0] != '\0' ? uri : nullptr;
}

bool
Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
{
    const BIO_Pointer in(BIO_new_file(certsFile, "r"));
    if (!in) {
        debugs(83, DBG_IMPORTANT, "ERROR: Failed to open '" << certsFile << "' to load certificates");
        return false;
    }

    while (auto aCert = ReadOptionalCertificate(in)) {
        const auto name = Security::SubjectName(*aCert);
        list.insert(std::pair<SBuf, X509 *>(name, aCert.release()));
    }
    debugs(83, 4, "Loaded " << list.size() << " certificates from file: '" << certsFile << "'");
    return true;
}

/// quickly find the issuer certificate of a certificate cert in the
/// Ssl::CertsIndexedList list
static X509 *
findCertIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
{
    const auto name = Security::IssuerName(*cert);
    if (name.isEmpty())
        return nullptr;

    const auto ret = list.equal_range(name);
    for (Ssl::CertsIndexedList::iterator it = ret.first; it != ret.second; ++it) {
        X509 *issuer = it->second;
        if (Security::IssuedBy(*cert, *issuer)) {
            return issuer;
        }
    }
    return nullptr;
}

/// slowly find the issuer certificate of a given cert using linear search
static X509 *
sk_x509_findIssuer(const STACK_OF(X509) *sk, X509 *cert)
{
    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 (Security::IssuedBy(*cert, *issuer))
            return issuer;
    }
    return nullptr;
}

/// 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 (!connContext)
        return nullptr;

    X509_STORE_CTX *storeCtx = X509_STORE_CTX_new();
    if (!storeCtx) {
        debugs(83, DBG_IMPORTANT, "ERROR: 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);
            debugs(83, 5, "found " << *issuer);
        } 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, "ERROR: Failed to initialize STORE_CTX object: " << Security::ErrorString(ssl_error));
    }

    X509_STORE_CTX_free(storeCtx);

    return issuer;
}

Security::CertPointer
Ssl::findIssuerCertificate(X509 *cert, const STACK_OF(X509) *serverCertificates, const Security::ContextPointer &context)
{
    Must(cert);

    // 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);
}

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 {
            debugs(83, 3, "Issuer certificate for " <<
                   *cert <<
                   " is missing and its URI is not provided");
        }
    }

    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)
{
    debugs(83, 2,  "completing " << sk_X509_num(&untrustedCerts) <<
           " OpenSSL untrusted certs using " << SquidUntrustedCerts.size() <<
           " configured untrusted certificates");

    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 (Security::SelfSigned(*current)) {
            // either ctx->cert is itself self-signed or untrustedCerts
            // already contain the self-signed current certificate
            break;
        }

        // untrustedCerts is short, not worth indexing
        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);
}

/// Validates certificates while consulting sslproxy_foreign_intermediate_certs
/// and, optionally, the given extra certificates.
/// \returns whatever OpenSSL X509_verify_cert() returns
static int
VerifyCtxCertificates(X509_STORE_CTX *ctx, STACK_OF(X509) *extraCerts)
{
    // 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 = 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, *untrustedCerts);

    X509_STORE_CTX_set0_untrusted(ctx, untrustedCerts.get()); // No locking/unlocking, just sets ctx->untrusted
    int ret = X509_verify_cert(ctx);
    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)
{
    SSL_CTX_set_cert_verify_callback(sslContext, untrustedToStoreCtx_cb, nullptr);
}

bool
Ssl::loadSquidUntrusted(const char *path)
{
    return Ssl::loadCerts(path, SquidUntrustedCerts);
}

void
Ssl::unloadSquidUntrusted()
{
    if (SquidUntrustedCerts.size()) {
        for (Ssl::CertsIndexedList::iterator it = SquidUntrustedCerts.begin(); it != SquidUntrustedCerts.end(); ++it) {
            X509_free(it->second);
        }
        SquidUntrustedCerts.clear();
    }
}

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;
    if (const char *cn = CommonHostName(cert.get())) {
        certProperties.commonName = "Not trusted by \"";
        certProperties.commonName += cn;
        certProperties.commonName += "\"";
    } else if (const char *org = getOrganization(cert.get())) {
        certProperties.commonName =  "Not trusted by \"";
        certProperties.commonName += org;
        certProperties.commonName += "\"";
    } else
        certProperties.commonName =  "Not trusted";
    certProperties.setCommonName = true;
    // O, OU, and other CA subject fields will be mimicked
    // Expiration date and other common properties will be mimicked
    certProperties.signAlgorithm = Ssl::algSignSelf;
    certProperties.signWithPkey.resetAndLock(pkey.get());
    certProperties.mimicCert.resetAndLock(cert.get());
    return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties);
}

void Ssl::InRamCertificateDbKey(const Ssl::CertificateProperties &certProperties, SBuf &key)
{
    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);
        }
    }

    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 int
bio_sbuf_create(BIO* bio)
{
    BIO_set_init(bio, 0);
    BIO_set_data(bio, nullptr);
    return 1;
}

static int
bio_sbuf_destroy(BIO* bio)
{
    if (!bio)
        return 0;
    return 1;
}

static 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;
}

static 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;
}

static long
bio_sbuf_ctrl(BIO *bio, int cmd, long /* num */, void *)
{
    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;
    }
}

BIO *Ssl::BIO_new_SBuf(SBuf *buf)
{
#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 */

