/*
- * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "acl/FilledChecklist.h"
#include "anyp/PortCfg.h"
+#include "anyp/Uri.h"
#include "fatal.h"
#include "fd.h"
#include "fde.h"
#include "globals.h"
#include "ipc/MemMap.h"
#include "security/CertError.h"
+#include "security/ErrorDetail.h"
#include "security/Session.h"
#include "SquidConfig.h"
#include "SquidTime.h"
#include "ssl/ErrorDetail.h"
#include "ssl/gadgets.h"
#include "ssl/support.h"
-#include "URL.h"
#include <cerrno>
// TODO: Move ssl_ex_index_* global variables from global.cc here.
-int ssl_ex_index_ssl_untrusted_chain = -1;
+static int ssl_ex_index_verify_callback_parameters = -1;
static Ssl::CertsIndexedList SquidUntrustedCerts;
\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))
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);
}
}
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;
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;
return matchX509CommonNames(cert, (void *)server, check_domain);
}
-#if !HAVE_LIBCRYPTO_X509_STORE_CTX_GET0_CERT
-static inline X509 *X509_STORE_CTX_get0_cert(X509_STORE_CTX *ctx)
-{
- return ctx->cert;
-}
-#endif
-
-/// \ingroup ServerProtocolSSLInternal
+/// adjusts OpenSSL validation results for each verified certificate in ctx
+/// OpenSSL "verify_callback function" (\ref OpenSSL_vcb_disambiguation)
static int
ssl_verify_cb(int ok, X509_STORE_CTX * ctx)
{
}
}
+ 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));
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;
}
broken_cert.resetAndLock(last_used_cert);
}
- auto *errDetail = new Ssl::ErrorDetail(error_no, peer_cert.get(), broken_cert.get());
- if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, errDetail)) {
- debugs(83, 2, "Failed to set Ssl::ErrorDetail in ssl_verify_cb: Certificate " << buffer);
- delete errDetail;
- }
+ std::unique_ptr<Security::ErrorDetail::Pointer> edp(new Security::ErrorDetail::Pointer(
+ new Security::ErrorDetail(error_no, peer_cert, broken_cert)));
+ if (SSL_set_ex_data(ssl, ssl_ex_index_ssl_error_detail, edp.get()))
+ edp.release();
+ else
+ debugs(83, 2, "failed to store a " << buffer << " error detail: " << *edp);
}
return ok;
}
void
-Ssl::SetupVerifyCallback(Security::ContextPointer &ctx)
+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)
{
- SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb);
+ 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")
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;
}
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)
{
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;
fatalf("Unable to find SSL engine '%s'\n", ::Config.SSL.ssl_engine);
if (!ENGINE_set_default(e, ENGINE_METHOD_ALL)) {
- const int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
fatalf("Failed to initialise SSL engine: %s\n", Security::ErrorString(ssl_error));
}
}
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);
}
bool
if (!ctx)
return false;
- if (!SSL_CTX_use_certificate(ctx.get(), port.secure.signingCert.get())) {
- const int ssl_error = ERR_get_error();
- const auto &keys = port.secure.certs.front();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS certificate '" << keys.certFile << "': " << Security::ErrorString(ssl_error));
- return false;
- }
-
- if (!SSL_CTX_use_PrivateKey(ctx.get(), port.secure.signPkey.get())) {
- const int ssl_error = ERR_get_error();
- const auto &keys = port.secure.certs.front();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS private key '" << keys.privateKeyFile << "': " << Security::ErrorString(ssl_error));
- return false;
- }
-
- Ssl::addChainToSslContext(ctx, port.secure.certsToChain);
-
- if (!port.secure.updateContextConfig(ctx)) {
- debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context");
- return false;
- }
-
return true;
}
bool
-Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, long fl)
+Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, Security::ParsedPortFlags fl)
{
if (!ctx)
return false;
const char *cipher = peer.sslCipher.c_str();
if (!SSL_CTX_set_cipher_list(ctx.get(), cipher)) {
- const int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
fatalf("Failed to set SSL cipher suite '%s': %s\n",
cipher, Security::ErrorString(ssl_error));
}
const char *certfile = keys.certFile.c_str();
if (!SSL_CTX_use_certificate_chain_file(ctx.get(), certfile)) {
- const int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
fatalf("Failed to acquire SSL certificate '%s': %s\n",
certfile, Security::ErrorString(ssl_error));
}
ssl_ask_password(ctx.get(), keyfile);
if (!SSL_CTX_use_PrivateKey_file(ctx.get(), keyfile, SSL_FILETYPE_PEM)) {
- const int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
fatalf("Failed to acquire SSL private key '%s': %s\n",
keyfile, Security::ErrorString(ssl_error));
}
debugs(83, 5, "Comparing private and public SSL keys.");
if (!SSL_CTX_check_private_key(ctx.get())) {
- const int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
fatalf("SSL private key '%s' does not match public key '%s': %s\n",
certfile, keyfile, Security::ErrorString(ssl_error));
}
MaybeSetupRsaCallback(ctx);
- if (fl & SSL_FLAG_DONT_VERIFY_PEER) {
- debugs(83, 2, "SECURITY WARNING: Peer certificates are not verified for validity!");
- SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, NULL);
- } else {
- debugs(83, 9, "Setting certificate verification callback.");
- Ssl::SetupVerifyCallback(ctx);
- }
+ Ssl::ConfigurePeerVerification(ctx, fl);
return true;
}
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)
return sslGetUserAttribute(ssl, "emailAddress");
}
-const char *
+SBuf
sslGetUserCertificatePEM(SSL *ssl)
{
- X509 *cert;
- BIO *mem;
- static char *str = NULL;
- char *ptr;
- long len;
-
- safe_free(str);
-
- if (!ssl)
- return NULL;
-
- cert = SSL_get_peer_certificate(ssl);
-
- if (!cert)
- return NULL;
-
- mem = BIO_new(BIO_s_mem());
-
- PEM_write_bio_X509(mem, cert);
-
- len = BIO_get_mem_data(mem, &ptr);
-
- str = (char *)xmalloc(len + 1);
-
- memcpy(str, ptr, len);
+ assert(ssl);
- str[len] = '\0';
+ if (const auto cert = SSL_get_peer_certificate(ssl))
+ return Ssl::GetX509PEM(cert);
- X509_free(cert);
-
- BIO_free(mem);
-
- return str;
+ return SBuf();
}
-const char *
+SBuf
sslGetUserCertificateChainPEM(SSL *ssl)
{
- STACK_OF(X509) *chain;
- BIO *mem;
- static char *str = NULL;
- char *ptr;
- long len;
- int i;
-
- safe_free(str);
+ assert(ssl);
- if (!ssl)
- return NULL;
-
- chain = SSL_get_peer_cert_chain(ssl);
+ auto chain = SSL_get_peer_cert_chain(ssl);
if (!chain)
return sslGetUserCertificatePEM(ssl);
- mem = BIO_new(BIO_s_mem());
+ Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
- for (i = 0; i < sk_X509_num(chain); ++i) {
+ for (int i = 0; i < sk_X509_num(chain); ++i) {
X509 *cert = sk_X509_value(chain, i);
- PEM_write_bio_X509(mem, cert);
+ PEM_write_bio_X509(bio.get(), cert);
}
- len = BIO_get_mem_data(mem, &ptr);
-
- str = (char *)xmalloc(len + 1);
- memcpy(str, ptr, len);
- str[len] = '\0';
-
- BIO_free(mem);
-
- return str;
+ char *ptr;
+ const auto len = BIO_get_mem_data(bio.get(), &ptr);
+ return SBuf(ptr, len);
}
/// Create SSL context and apply ssl certificate and private key to it.
{
assert(ctx);
// Add signing certificate to the certificates chain
- X509 *signingCert = options.signingCert.get();
+ X509 *signingCert = options.signingCa.cert.get();
if (SSL_CTX_add_extra_chain_cert(ctx.get(), signingCert)) {
// increase the certificate lock
X509_up_ref(signingCert);
} else {
- const int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << Security::ErrorString(ssl_error));
}
- Ssl::addChainToSslContext(ctx, options.certsToChain);
+
+ 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
}
bool
-Ssl::verifySslCertificate(Security::ContextPointer &ctx, CertificateProperties const &properties)
+Ssl::verifySslCertificate(const Security::ContextPointer &ctx, CertificateProperties const &properties)
{
#if HAVE_SSL_CTX_GET0_CERTIFICATE
X509 * cert = SSL_CTX_get0_certificate(ctx.get());
#endif
if (!cert)
return false;
- ASN1_TIME * time_notBefore = X509_get_notBefore(cert);
- ASN1_TIME * time_notAfter = X509_get_notAfter(cert);
+ const auto time_notBefore = X509_getm_notBefore(cert);
+ const auto time_notAfter = X509_getm_notAfter(cert);
return (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0);
}
-bool
+void
Ssl::setClientSNI(SSL *ssl, const char *fqdn)
{
+ const Ip::Address test(fqdn);
+ if (!test.isAnyAddr())
+ return; // raw IP is inappropriate for SNI
+
//The SSL_CTRL_SET_TLSEXT_HOSTNAME is a openssl macro which indicates
// if the TLS servername extension (SNI) is enabled in openssl library.
#if defined(SSL_CTRL_SET_TLSEXT_HOSTNAME)
if (!SSL_set_tlsext_host_name(ssl, fqdn)) {
- const int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
debugs(83, 3, "WARNING: unable to set TLS servername extension (SNI): " <<
Security::ErrorString(ssl_error) << "\n");
- return false;
}
- return true;
#else
debugs(83, 7, "no support for TLS servername extension (SNI)");
- return false;
#endif
}
-void
-Ssl::addChainToSslContext(Security::ContextPointer &ctx, Security::CertList &chain)
-{
- if (chain.empty())
- return;
-
- for (auto cert : chain) {
- if (SSL_CTX_add_extra_chain_cert(ctx.get(), cert.get())) {
- // increase the certificate lock
- X509_up_ref(cert.get());
- } else {
- const int ssl_error = ERR_get_error();
- debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(ssl_error));
- }
- }
-}
-
-static const char *
-hasAuthorityInfoAccessCaIssuers(X509 *cert)
+const char *
+Ssl::findIssuerUri(X509 *cert)
{
AUTHORITY_INFO_ACCESS *info;
if (!cert)
if (ad->location->type == GEN_URI) {
xstrncpy(uri,
reinterpret_cast<const char *>(
-#if HAVE_LIBCRYPTO_ASN1_STRING_GET0_DATA
ASN1_STRING_get0_data(ad->location->d.uniformResourceIdentifier)
-#else
- ASN1_STRING_data(ad->location->d.uniformResourceIdentifier)
-#endif
),
sizeof(uri));
}
}
/// slowly find the issuer certificate of a given cert using linear search
-static bool
-findCertIssuer(Security::CertList const &list, X509 *cert)
+static X509 *
+sk_x509_findIssuer(const STACK_OF(X509) *sk, X509 *cert)
{
- for (Security::CertList::const_iterator it = list.begin(); it != list.end(); ++it) {
- if (X509_check_issued(it->get(), cert) == X509_V_OK)
- return true;
+ if (!sk)
+ return nullptr;
+
+ const auto certCount = sk_X509_num(sk);
+ for (int i = 0; i < certCount; ++i) {
+ const auto issuer = sk_X509_value(sk, i);
+ if (X509_check_issued(issuer, cert) == X509_V_OK)
+ return issuer;
}
- return false;
+ return nullptr;
}
-/// \return true if the cert issuer exist in the certificates stored in connContext
-static bool
-issuerExistInCaDb(X509 *cert, const Security::ContextPointer &connContext)
+/// 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 false;
+ return nullptr;
X509_STORE_CTX *storeCtx = X509_STORE_CTX_new();
if (!storeCtx) {
debugs(83, DBG_IMPORTANT, "Failed to allocate STORE_CTX object");
- return false;
+ return nullptr;
}
- bool gotIssuer = false;
+ X509 *issuer = nullptr;
X509_STORE *store = SSL_CTX_get_cert_store(connContext.get());
if (X509_STORE_CTX_init(storeCtx, store, nullptr, nullptr)) {
- X509 *issuer = nullptr;
- gotIssuer = (X509_STORE_CTX_get1_issuer(&issuer, storeCtx, cert) > 0);
- if (issuer)
- X509_free(issuer);
+ 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 int ssl_error = ERR_get_error();
+ const auto ssl_error = ERR_get_error();
debugs(83, DBG_IMPORTANT, "Failed to initialize STORE_CTX object: " << Security::ErrorString(ssl_error));
}
+
X509_STORE_CTX_free(storeCtx);
- return gotIssuer;
+ return issuer;
}
-const char *
-Ssl::uriOfIssuerIfMissing(X509 *cert, Security::CertList const &serverCertificates, const Security::ContextPointer &context)
+Security::CertPointer
+Ssl::findIssuerCertificate(X509 *cert, const STACK_OF(X509) *serverCertificates, const Security::ContextPointer &context)
{
- if (!cert || !serverCertificates.size())
- return nullptr;
+ Must(cert);
- if (!findCertIssuer(serverCertificates, cert)) {
- //if issuer is missing
- if (const char *issuerUri = hasAuthorityInfoAccessCaIssuers(cert)) {
- // There is a URI where we can download a certificate.
- if (!findCertIssuerFast(SquidUntrustedCerts, cert) &&
- !issuerExistInCaDb(cert, context)) {
- // and issuer not found in local databases containing
- // untrusted certificates and trusted CA certificates
- return 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);
}
- return nullptr;
-}
-
-void
-Ssl::missingChainCertificatesUrls(std::queue<SBuf> &URIs, Security::CertList const &serverCertificates, const Security::ContextPointer &context)
-{
- if (!serverCertificates.size())
- return;
- for (const auto &i : serverCertificates) {
- if (const char *issuerUri = uriOfIssuerIfMissing(i.get(), serverCertificates, context))
- URIs.push(SBuf(issuerUri));
+ // check untrusted certificates
+ if (const auto issuer = findCertIssuerFast(SquidUntrustedCerts, cert)) {
+ X509_up_ref(issuer);
+ return Security::CertPointer(issuer);
}
-}
-void
-Ssl::SSL_add_untrusted_cert(SSL *ssl, X509 *cert)
-{
- STACK_OF(X509) *untrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
- if (!untrustedStack) {
- untrustedStack = sk_X509_new_null();
- if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain, untrustedStack)) {
- sk_X509_pop_free(untrustedStack, X509_free);
- throw TextException("Failed to attach untrusted certificates chain", Here());
- }
+ // 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);
}
- sk_X509_push(untrustedStack, cert);
+
+ return Security::CertPointer(nullptr);
}
-/// Search for the issuer certificate of cert in sk list.
-static X509 *
-sk_x509_findIssuer(STACK_OF(X509) *sk, X509 *cert)
+bool
+Ssl::missingChainCertificatesUrls(std::queue<SBuf> &URIs, const STACK_OF(X509) &serverCertificates, const Security::ContextPointer &context)
{
- if (!sk)
- return NULL;
+ for (int i = 0; i < sk_X509_num(&serverCertificates); ++i) {
+ const auto cert = sk_X509_value(&serverCertificates, i);
- const int skItemsNum = sk_X509_num(sk);
- for (int i = 0; i < skItemsNum; ++i) {
- X509 *issuer = sk_X509_value(sk, i);
- if (X509_check_issued(issuer, cert) == X509_V_OK)
- return issuer;
+ if (findIssuerCertificate(cert, &serverCertificates, context))
+ continue;
+
+ if (const auto issuerUri = findIssuerUri(cert)) {
+ URIs.push(SBuf(issuerUri));
+ } else {
+ static char name[2048];
+ debugs(83, 3, "Issuer certificate for " <<
+ X509_NAME_oneline(X509_get_subject_name(cert), name, sizeof(name)) <<
+ " is missing and its URI is not provided");
+ }
}
- return NULL;
+
+ debugs(83, (URIs.empty() ? 3 : 5), "found: " << URIs.size());
+ return !URIs.empty();
}
/// add missing issuer certificates to untrustedCerts
static void
-completeIssuers(X509_STORE_CTX *ctx, STACK_OF(X509) *untrustedCerts)
+completeIssuers(X509_STORE_CTX *ctx, STACK_OF(X509) &untrustedCerts)
{
- debugs(83, 2, "completing " << sk_X509_num(untrustedCerts) << " OpenSSL untrusted certs using " << SquidUntrustedCerts.size() << " configured untrusted certificates");
+ debugs(83, 2, "completing " << sk_X509_num(&untrustedCerts) <<
+ " OpenSSL untrusted certs using " << SquidUntrustedCerts.size() <<
+ " configured untrusted certificates");
-#if HAVE_LIBCRYPTO_X509_VERIFY_PARAM_GET_DEPTH
const X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx);
int depth = X509_VERIFY_PARAM_get_depth(param);
-#else
- int depth = ctx->param->depth;
-#endif
- X509 *current = X509_STORE_CTX_get0_cert(ctx);
+ Security::CertPointer current;
+ current.resetAndLock(X509_STORE_CTX_get0_cert(ctx));
int i = 0;
for (i = 0; current && (i < depth); ++i) {
- if (X509_check_issued(current, current) == X509_V_OK) {
+ if (X509_check_issued(current.get(), current.get()) == X509_V_OK) {
// either ctx->cert is itself self-signed or untrustedCerts
- // aready contain the self-signed current certificate
+ // already contain the self-signed current certificate
break;
}
// untrustedCerts is short, not worth indexing
- X509 *issuer = sk_x509_findIssuer(untrustedCerts, current);
- if (!issuer) {
- if ((issuer = findCertIssuerFast(SquidUntrustedCerts, current)))
- sk_X509_push(untrustedCerts, issuer);
- }
+ const Security::ContextPointer nullCtx;
+ auto issuer = Ssl::findIssuerCertificate(current.get(), &untrustedCerts, nullCtx);
current = issuer;
+ if (issuer)
+ sk_X509_push(&untrustedCerts, issuer.release());
}
if (i >= depth)
debugs(83, 2, "exceeded the maximum certificate chain length: " << depth);
}
-/// OpenSSL certificate validation callback.
+/// Validates certificates while consulting sslproxy_foreign_intermediate_certs
+/// and, optionally, the given extra certificates.
+/// \returns whatever OpenSSL X509_verify_cert() returns
static int
-untrustedToStoreCtx_cb(X509_STORE_CTX *ctx,void *data)
+VerifyCtxCertificates(X509_STORE_CTX *ctx, STACK_OF(X509) *extraCerts)
{
- debugs(83, 4, "Try to use pre-downloaded intermediate certificates");
-
- SSL *ssl = static_cast<SSL *>(X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
- STACK_OF(X509) *sslUntrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
-
// OpenSSL already maintains ctx->untrusted but we cannot modify
// internal OpenSSL list directly. We have to give OpenSSL our own
// list, but it must include certificates on the OpenSSL ctx->untrusted
-#if HAVE_LIBCRYPTO_X509_STORE_CTX_GET0_UNTRUSTED
STACK_OF(X509) *oldUntrusted = X509_STORE_CTX_get0_untrusted(ctx);
-#else
- STACK_OF(X509) *oldUntrusted = ctx->untrusted;
-#endif
- STACK_OF(X509) *sk = sk_X509_dup(oldUntrusted); // oldUntrusted is always not NULL
-
- for (int i = 0; i < sk_X509_num(sslUntrustedStack); ++i) {
- X509 *cert = sk_X509_value(sslUntrustedStack, i);
- sk_X509_push(sk, cert);
+ // 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);
-#if HAVE_LIBCRYPTO_X509_STORE_CTX_SET0_UNTRUSTED
- X509_STORE_CTX_set0_untrusted(ctx, oldUntrusted);
-#else
- X509_STORE_CTX_set_chain(ctx, oldUntrusted); // Set back the old untrusted list
-#endif
- sk_X509_free(sk); // Release sk list
+ X509_STORE_CTX_set0_untrusted(ctx, oldUntrusted); // Set back the old untrusted list
return ret;
}
+/// \interface OpenSSL_vcb_disambiguation
+///
+/// OpenSSL has two very different concepts with nearly identical names:
+///
+/// a) A (replaceable) certificate verification function -- X509_verify_cert():
+/// This function drives the entire certificate verification algorithm.
+/// It can be called directly, but is usually called during SSL_connect().
+/// OpenSSL calls this function a "verification callback function".
+/// SSL_CTX_set_cert_verify_callback(3) replaces X509_verify_cert() default.
+///
+/// b) An (optional) certificate verification adjustment callback:
+/// This function, if set, is called at the end of (a) to adjust (a) results.
+/// It is never called directly, only from (a).
+/// OpenSSL calls this function a "verify_callback function".
+/// The SSL_CTX_set_verify(3) family of functions sets this function.
+
+/// Validates certificates while consulting sslproxy_foreign_intermediate_certs
+/// but without using any dynamically downloaded intermediate certificates.
+/// OpenSSL "verification callback function" (\ref OpenSSL_vcb_disambiguation)
+static int
+untrustedToStoreCtx_cb(X509_STORE_CTX *ctx, void *)
+{
+ debugs(83, 4, "Try to use pre-downloaded intermediate certificates");
+ return VerifyCtxCertificates(ctx, nullptr);
+}
+
void
Ssl::useSquidUntrusted(SSL_CTX *sslContext)
{
}
}
-/**
- \ingroup ServerProtocolSSLInternal
- * Read certificate from file.
- * See also: static readSslX509Certificate function, gadgets.cc file
- */
-static X509 * readSslX509CertificatesChain(char const * certFilename, Security::CertList &chain)
-{
- if (!certFilename)
- return NULL;
- Ssl::BIO_Pointer bio(BIO_new(BIO_s_file()));
- if (!bio)
- return NULL;
- if (!BIO_read_filename(bio.get(), certFilename))
- return NULL;
- X509 *certificate = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL);
-
- if (certificate) {
-
- 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(), nullptr, nullptr, nullptr))
- chain.emplace_front(Security::CertPointer(ca));
- }
- }
-
- return certificate;
-}
-
-void
-Ssl::readCertChainAndPrivateKeyFromFiles(Security::CertPointer & cert, Security::PrivateKeyPointer & pkey, Security::CertList &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);
-
- // 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;
- Ssl::ReadPrivateKeyFromFile(keyFilename, pkey, cb);
- cert.resetWithoutLocking(readSslX509CertificatesChain(certFilename, chain));
- if (!cert) {
- debugs(83, DBG_IMPORTANT, "WARNING: missing cert in '" << certFilename << "'");
- } else if (!pkey) {
- debugs(83, DBG_IMPORTANT, "WARNING: missing private key in '" << keyFilename << "'");
- } else if (!X509_check_private_key(cert.get(), pkey.get())) {
- debugs(83, DBG_IMPORTANT, "WARNING: X509_check_private_key() failed to verify signing cert");
- } else
- return; // everything is okay
-
- pkey.reset();
- cert.reset();
-}
-
bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, Security::PrivateKeyPointer &untrustedPkey, Security::CertPointer const &cert, Security::PrivateKeyPointer const & pkey)
{
// Generate the self-signed certificate, using a hard-coded subject prefix