/*
- * 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>
-Ipc::MemMap *Ssl::SessionCache = NULL;
-const char *Ssl::SessionCacheName = "ssl_session_cache";
+// 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 = NULL;
-const char *Ssl::BumpModeStr[] = {
+std::vector<const char *> Ssl::BumpModeStr = {
"none",
"client-first",
"server-first",
"stare",
"bump",
"splice",
- "terminate",
- /*"err",*/
- NULL
+ "terminate"
+ /*,"err"*/
};
/**
\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);
}
}
-/// \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;
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 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)
{
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) {
}
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)
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));
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;
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);
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 {
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;
}
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.
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;
}
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;
}
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;
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
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);
-}
-
-#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)
-{
- 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);
-
- return true;
+ ssl_ex_index_verify_callback_parameters = SSL_get_ex_new_index(0, (void *) "verify_callback_parameters", nullptr, nullptr, &ssl_free_VerifyCallbackParameters);
}
bool
-Ssl::InitServerContext(const Security::ContextPointer &ctx, AnyP::PortCfg &port)
+Ssl::InitServerContext(Security::ContextPointer &ctx, AnyP::PortCfg &port)
{
if (!ctx)
return false;
- if (!SSL_CTX_use_certificate(ctx.get(), port.signingCert.get())) {
- const int ssl_error = ERR_get_error();
- const auto &keys = port.secure.certs.front();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS certificate '" << keys.certFile << "': " << ERR_error_string(ssl_error, NULL));
- return false;
- }
-
- if (!SSL_CTX_use_PrivateKey(ctx.get(), port.signPkey.get())) {
- const int ssl_error = ERR_get_error();
- const auto &keys = port.secure.certs.front();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire TLS private key '" << keys.privateKeyFile << "': " << ERR_error_string(ssl_error, NULL));
- return false;
- }
-
- Ssl::addChainToSslContext(ctx.get(), port.certsToChain.get());
-
- /* Alternate code;
- debugs(83, DBG_IMPORTANT, "Using certificate in " << certfile);
-
- if (!SSL_CTX_use_certificate_chain_file(ctx.get(), certfile)) {
- ssl_error = ERR_get_error();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL certificate '" << certfile << "': " << ERR_error_string(ssl_error, NULL));
- return false;
- }
-
- debugs(83, DBG_IMPORTANT, "Using private key in " << keyfile);
- ssl_ask_password(ctx.get(), keyfile);
-
- if (!SSL_CTX_use_PrivateKey_file(ctx.get(), keyfile, SSL_FILETYPE_PEM)) {
- ssl_error = ERR_get_error();
- debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL private key '" << keyfile << "': " << ERR_error_string(ssl_error, NULL));
- return false;
- }
-
- debugs(83, 5, "Comparing private and public SSL keys.");
-
- if (!SSL_CTX_check_private_key(ctx.get())) {
- ssl_error = ERR_get_error();
- debugs(83, DBG_CRITICAL, "ERROR: SSL private key '" << certfile << "' does not match public key '" <<
- keyfile << "': " << ERR_error_string(ssl_error, NULL));
- return false;
- }
- */
-
- if (!configureSslContext(ctx.get(), port)) {
- debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context");
- return false;
- }
-
return true;
}
bool
-Ssl::InitClientContext(Security::ContextPtr &sslContext, Security::PeerOptions &peer, long options, long fl)
+Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, Security::ParsedPortFlags fl)
{
- if (!sslContext)
+ if (!ctx)
return false;
- SSL_CTX_set_options(sslContext, options);
-
-#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
- SSL_CTX_set_info_callback(sslContext, ssl_info_cb);
-#endif
-
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));
}
}
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);
+ MaybeSetupRsaCallback(ctx);
- 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);
- }
+ Ssl::ConfigurePeerVerification(ctx, fl);
return true;
}
-/// \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;
- }
-
- int i = SSL_write(ssl, buf, len);
- return i;
-}
-
-void
-ssl_shutdown_method(SSL *ssl)
-{
- SSL_shutdown(ssl);
-}
-
/// \ingroup ServerProtocolSSLInternal
static const char *
ssl_get_attribute(X509_NAME * name, const char *attribute_name)
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);
+ assert(ssl);
- memcpy(str, ptr, len);
+ if (const auto cert = SSL_get_peer_certificate(ssl))
+ return Ssl::GetX509PEM(cert);
- str[len] = '\0';
-
- X509_free(cert);
-
- BIO_free(mem);
-
- return str;
+ return SBuf();
}
-const char *
+SBuf
sslGetUserCertificateChainPEM(SSL *ssl)
{
- STACK_OF(X509) *chain;
- BIO *mem;
- static char *str = NULL;
- char *ptr;
- long len;
- int i;
+ assert(ssl);
- safe_free(str);
-
- if (!ssl)
- return NULL;
-
- chain = SSL_get_peer_cert_chain(ssl);
+ auto chain = SSL_get_peer_cert_chain(ssl);
if (!chain)
return sslGetUserCertificatePEM(ssl);
- mem = BIO_new(BIO_s_mem());
+ Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
- for (i = 0; i < sk_X509_num(chain); ++i) {
+ for (int i = 0; i < sk_X509_num(chain); ++i) {
X509 *cert = sk_X509_value(chain, i);
- PEM_write_bio_X509(mem, cert);
+ PEM_write_bio_X509(bio.get(), cert);
}
- len = BIO_get_mem_data(mem, &ptr);
-
- str = (char *)xmalloc(len + 1);
- memcpy(str, ptr, len);
- str[len] = '\0';
-
- BIO_free(mem);
-
- return str;
+ char *ptr;
+ const auto len = BIO_get_mem_data(bio.get(), &ptr);
+ return SBuf(ptr, len);
}
/// Create SSL context and apply ssl certificate and private key to it.
-Security::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(SSL_CTX *sslContext, AnyP::PortCfg &port)
+Ssl::chainCertificatesToSSLContext(Security::ContextPointer &ctx, Security::ServerOptions &options)
{
- assert(sslContext != NULL);
+ assert(ctx);
// Add signing certificate to the certificates chain
- X509 *signingCert = port.signingCert.get();
- if (SSL_CTX_add_extra_chain_cert(sslContext, signingCert)) {
+ X509 *signingCert = options.signingCa.cert.get();
+ if (SSL_CTX_add_extra_chain_cert(ctx.get(), signingCert)) {
// increase the certificate lock
- CRYPTO_add(&(signingCert->references),1,CRYPTO_LOCK_X509);
+ X509_up_ref(signingCert);
} else {
- const int ssl_error = ERR_get_error();
- debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL));
+ 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));
+ }
}
- Ssl::addChainToSslContext(sslContext, port.certsToChain.get());
}
void
-Ssl::configureUnconfiguredSslContext(SSL_CTX *sslContext, Ssl::CertSignAlgorithm signAlgorithm,AnyP::PortCfg &port)
+Ssl::configureUnconfiguredSslContext(Security::ContextPointer &ctx, Ssl::CertSignAlgorithm signAlgorithm,AnyP::PortCfg &port)
{
- if (sslContext && signAlgorithm == Ssl::algSignTrusted) {
- Ssl::chainCertificatesToSSLContext(sslContext, 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;
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;
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)
+const char *
+Ssl::findIssuerUri(X509 *cert)
{
- if (!chain)
- return;
+ AUTHORITY_INFO_ACCESS *info;
+ if (!cert)
+ return nullptr;
+ info = static_cast<AUTHORITY_INFO_ACCESS *>(X509_get_ext_d2i(cert, NID_info_access, NULL, NULL));
+ if (!info)
+ return nullptr;
- 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 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
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(X509_STORE_CTX *ctx, Ssl::CertsIndexedList &list, X509 *cert)
+findCertIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
{
static char buffer[2048];
const auto ret = list.equal_range(SBuf(buffer));
for (Ssl::CertsIndexedList::iterator it = ret.first; it != ret.second; ++it) {
X509 *issuer = it->second;
- if (ctx->check_issued(ctx, 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(X509_STORE_CTX *ctx, STACK_OF(X509) *sk, X509 *cert)
+sk_x509_findIssuer(const STACK_OF(X509) *sk, X509 *cert)
{
- const int skItemsNum = sk_X509_num(sk);
- for (int i = 0; i < skItemsNum; ++i) {
- X509 *issuer = sk_X509_value(sk, i);
- if (ctx->check_issued(ctx, cert, issuer))
+ 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 NULL;
+ 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, "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));
+ }
+
+ 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 {
+ 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");
+ }
+ }
+
+ 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 (ctx->check_issued(ctx, 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(ctx, untrustedCerts, current);
- if (!issuer) {
- if ((issuer = findCertByIssuerFast(ctx, 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");
-
// 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
- completeIssuers(ctx, sk);
- X509_STORE_CTX_set_chain(ctx, sk); // No locking/unlocking, just sets 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_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)
{
- if (SquidUntrustedCerts.size() > 0)
- SSL_CTX_set_cert_verify_callback(sslContext, untrustedToStoreCtx_cb, NULL);
- else
- SSL_CTX_set_cert_verify_callback(sslContext, NULL, NULL);
+ SSL_CTX_set_cert_verify_callback(sslContext, untrustedToStoreCtx_cb, NULL);
}
bool
}
}
-/**
- \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.resetWithoutLocking(readSslPrivateKey(keyFilename, cb));
- cert.resetWithoutLocking(readSslX509CertificatesChain(certFilename, chain.get()));
- if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) {
- pkey.reset();
- cert.reset();
- }
-}
-
-bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, EVP_PKEY_Pointer &untrustedPkey, Security::CertPointer const &cert, EVP_PKEY_Pointer const & pkey)
+bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, Security::PrivateKeyPointer &untrustedPkey, Security::CertPointer const &cert, Security::PrivateKeyPointer const & pkey)
{
// Generate the self-signed certificate, using a hard-coded subject prefix
Ssl::CertificateProperties certProperties;
return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties);
}
-static bool
-SslCreate(Security::ContextPtr sslContext, const Comm::ConnectionPointer &conn, Ssl::Bio::Type type, const char *squidCtx)
+void Ssl::InRamCertificateDbKey(const Ssl::CertificateProperties &certProperties, SBuf &key)
{
- if (!Comm::IsConnOpen(conn)) {
- debugs(83, DBG_IMPORTANT, "Gone connection");
- return false;
- }
-
- const char *errAction = NULL;
- int errCode = 0;
- if (auto ssl = SSL_new(sslContext)) {
- const int fd = conn->fd;
- // 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.resetWithoutLocking(ssl);
- fd_table[fd].read_method = &ssl_read_method;
- fd_table[fd].write_method = &ssl_write_method;
- fd_note(fd, squidCtx);
- return true;
+ 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 false;
-}
-
-bool
-Ssl::CreateClient(Security::ContextPtr sslContext, const Comm::ConnectionPointer &c, const char *squidCtx)
-{
- return SslCreate(sslContext, c, Ssl::Bio::BIO_TO_SERVER, squidCtx);
-}
-
-bool
-Ssl::CreateServer(Security::ContextPtr sslContext, const Comm::ConnectionPointer &c, const char *squidCtx)
-{
- return SslCreate(sslContext, c, 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 */