Skip to content

Commit

Permalink
Bug 5133: OpenSSL 3.0 support (#694)
Browse files Browse the repository at this point in the history
This TLS update includes:

* Fix build with OpenSSL v3.
* Refactor RSA key generation to avoid deprecated RSA_*() APIs.
* Refactor DH parameter and key config to avoid deprecated DH_*() APIs.
* Refactor ECDH key creation to avoid deprecated EC_*() APIs.
* Deprecate ssl_engine support in builds with OpenSSL v1-.
* Disable ssl_engine support in builds OpenSSL v3+.

We deprecated/removed ssl_engine support (as summarized in the last two
bullets above) without providing an OpenSSL Providers-based alternative
because of the following factors:

1. We do not have the resources to update ssl_engine code to build
   (without deprecation warnings) with OpenSSL v3 when the feature is
   unused.

2. We do not have the resources to create an OpenSSL v3 Provider-based
   replacement for ssl_engine code that uses deprecated Engine APIs.

3. OpenSSL v3 deprecated Engine support (triggering deprecation warnings
   in applications that use Engine APIs with OpenSSL v3). Since Squid
   default builds use -Werror, doing nothing would break such builds.

4. Squid ssl_engine does not appear to be a popular feature.
  • Loading branch information
yadij authored and squid-anubis committed Jul 17, 2022
1 parent f38db63 commit 742236c
Show file tree
Hide file tree
Showing 10 changed files with 176 additions and 80 deletions.
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,7 @@ if test "x$with_openssl" = "xyes"; then
openssl/bio.h \
openssl/bn.h \
openssl/crypto.h \
openssl/decoder.h \
openssl/dh.h \
openssl/err.h \
openssl/evp.h \
Expand Down
4 changes: 3 additions & 1 deletion doc/release-notes/release-6.sgml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ This section gives an account of those changes in three categories:
<sect1>Changes to existing directives<label id="modifieddirectives">
<p>
<descrip>
<p>There have been no directives changed.
<tag>ssl_engine</tag>
<p>OpenSSL 3.0.0 deprecates the Engine feature. This directive is
only supported when Squid is built for older OpenSSL versions.

</descrip>

Expand Down
2 changes: 2 additions & 0 deletions src/cf.data.pre
Original file line number Diff line number Diff line change
Expand Up @@ -3061,6 +3061,8 @@ DEFAULT: none
DOC_START
The OpenSSL engine to use. You will need to set this if you
would like to use hardware SSL acceleration for example.

Not supported in builds with OpenSSL v3 or newer.
DOC_END

NAME: sslproxy_session_ttl
Expand Down
2 changes: 2 additions & 0 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,9 @@ mainHandleCommandLineOption(const int optId, const char *optValue)
printf("%s\n",SQUID_BUILD_INFO);
#if USE_OPENSSL
printf("\nThis binary uses %s. ", OpenSSL_version(OPENSSL_VERSION));
#if OPENSSL_VERSION_MAJOR < 3
printf("For legal restrictions on distribution see https://www.openssl.org/source/license.html\n\n");
#endif
#endif
printf( "configure options: %s\n", SQUID_CONFIGURE_OPTIONS);

Expand Down
66 changes: 35 additions & 31 deletions src/security/PeerOptions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -293,134 +293,134 @@ Security::PeerOptions::createClientContext(bool setOptions)
/// set of options we can parse and what they map to
static struct ssl_option {
const char *name;
long value;
Security::ParsedOptions value;

} ssl_options[] = {

#if SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
#if defined(SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG)
{
"NETSCAPE_REUSE_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
},
#endif
#if SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
#if defined(SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG)
{
"SSLREF2_REUSE_CERT_TYPE_BUG", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
},
#endif
#if SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
#if defined(SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER)
{
"MICROSOFT_BIG_SSLV3_BUFFER", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
},
#endif
#if SSL_OP_SSLEAY_080_CLIENT_DH_BUG
#if defined(SSL_OP_SSLEAY_080_CLIENT_DH_BUG)
{
"SSLEAY_080_CLIENT_DH_BUG", SSL_OP_SSLEAY_080_CLIENT_DH_BUG
},
#endif
#if SSL_OP_TLS_D5_BUG
#if defined(SSL_OP_TLS_D5_BUG)
{
"TLS_D5_BUG", SSL_OP_TLS_D5_BUG
},
#endif
#if SSL_OP_TLS_BLOCK_PADDING_BUG
#if defined(SSL_OP_TLS_BLOCK_PADDING_BUG)
{
"TLS_BLOCK_PADDING_BUG", SSL_OP_TLS_BLOCK_PADDING_BUG
},
#endif
#if SSL_OP_TLS_ROLLBACK_BUG
#if defined(SSL_OP_TLS_ROLLBACK_BUG)
{
"TLS_ROLLBACK_BUG", SSL_OP_TLS_ROLLBACK_BUG
},
#endif
#if SSL_OP_ALL
#if defined(SSL_OP_ALL)
{
"ALL", (long)SSL_OP_ALL
},
#endif
#if SSL_OP_SINGLE_DH_USE
#if defined(SSL_OP_SINGLE_DH_USE)
{
"SINGLE_DH_USE", SSL_OP_SINGLE_DH_USE
},
#endif
#if SSL_OP_EPHEMERAL_RSA
#if defined(SSL_OP_EPHEMERAL_RSA)
{
"EPHEMERAL_RSA", SSL_OP_EPHEMERAL_RSA
},
#endif
#if SSL_OP_PKCS1_CHECK_1
#if defined(SSL_OP_PKCS1_CHECK_1)
{
"PKCS1_CHECK_1", SSL_OP_PKCS1_CHECK_1
},
#endif
#if SSL_OP_PKCS1_CHECK_2
#if defined(SSL_OP_PKCS1_CHECK_2)
{
"PKCS1_CHECK_2", SSL_OP_PKCS1_CHECK_2
},
#endif
#if SSL_OP_NETSCAPE_CA_DN_BUG
#if defined(SSL_OP_NETSCAPE_CA_DN_BUG)
{
"NETSCAPE_CA_DN_BUG", SSL_OP_NETSCAPE_CA_DN_BUG
},
#endif
#if SSL_OP_NON_EXPORT_FIRST
#if defined(SSL_OP_NON_EXPORT_FIRST)
{
"NON_EXPORT_FIRST", SSL_OP_NON_EXPORT_FIRST
},
#endif
#if SSL_OP_CIPHER_SERVER_PREFERENCE
#if defined(SSL_OP_CIPHER_SERVER_PREFERENCE)
{
"CIPHER_SERVER_PREFERENCE", SSL_OP_CIPHER_SERVER_PREFERENCE
},
#endif
#if SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
#if defined(SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG)
{
"NETSCAPE_DEMO_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG
},
#endif
#if SSL_OP_NO_SSLv3
#if defined(SSL_OP_NO_SSLv3)
{
"NO_SSLv3", SSL_OP_NO_SSLv3
},
#endif
#if SSL_OP_NO_TLSv1
#if defined(SSL_OP_NO_TLSv1)
{
"NO_TLSv1", SSL_OP_NO_TLSv1
},
#else
{ "NO_TLSv1", 0 },
#endif
#if SSL_OP_NO_TLSv1_1
#if defined(SSL_OP_NO_TLSv1_1)
{
"NO_TLSv1_1", SSL_OP_NO_TLSv1_1
},
#else
{ "NO_TLSv1_1", 0 },
#endif
#if SSL_OP_NO_TLSv1_2
#if defined(SSL_OP_NO_TLSv1_2)
{
"NO_TLSv1_2", SSL_OP_NO_TLSv1_2
},
#else
{ "NO_TLSv1_2", 0 },
#endif
#if SSL_OP_NO_TLSv1_3
#if defined(SSL_OP_NO_TLSv1_3)
{
"NO_TLSv1_3", SSL_OP_NO_TLSv1_3
},
#else
{ "NO_TLSv1_3", 0 },
#endif
#if SSL_OP_NO_COMPRESSION
#if defined(SSL_OP_NO_COMPRESSION)
{
"No_Compression", SSL_OP_NO_COMPRESSION
},
#endif
#if SSL_OP_NO_TICKET
#if defined(SSL_OP_NO_TICKET)
{
"NO_TICKET", SSL_OP_NO_TICKET
},
#endif
#if SSL_OP_SINGLE_ECDH_USE
#if defined(SSL_OP_SINGLE_ECDH_USE)
{
"SINGLE_ECDH_USE", SSL_OP_SINGLE_ECDH_USE
},
Expand Down Expand Up @@ -455,7 +455,7 @@ Security::PeerOptions::parseOptions()

#if USE_OPENSSL
::Parser::Tokenizer tok(str);
long op = 0;
ParsedOptions op = 0;

while (!tok.atEnd()) {
enum {
Expand All @@ -472,7 +472,8 @@ Security::PeerOptions::parseOptions()
static const CharacterSet optChars = CharacterSet("TLS-option", "_") + CharacterSet::ALPHA + CharacterSet::DIGIT;
int64_t hex = 0;
SBuf option;
long value = 0;
ParsedOptions value = 0;
bool found = false;

// Bug 4429: identify the full option name before determining text or numeric
if (tok.prefix(option, optChars)) {
Expand All @@ -481,14 +482,16 @@ Security::PeerOptions::parseOptions()
for (struct ssl_option *opttmp = ssl_options; opttmp->name; ++opttmp) {
if (option.cmp(opttmp->name) == 0) {
value = opttmp->value;
found = true;
break;
}
}

// Special case.. hex specification
::Parser::Tokenizer tmp(option);
if (!value && tmp.int64(hex, 16, false) && tmp.atEnd()) {
if (!found && tmp.int64(hex, 16, false) && tmp.atEnd()) {
value = hex;
found = true;
}
}

Expand All @@ -502,7 +505,7 @@ Security::PeerOptions::parseOptions()
break;
}
} else {
debugs(83, DBG_PARSE_NOTE(1), "ERROR: Unknown TLS option " << option);
debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT), "ERROR: " << (found?"Unsupported":"Unknown") << " TLS option " << option);
}

static const CharacterSet delims("TLS-option-delim",":,");
Expand All @@ -512,9 +515,10 @@ Security::PeerOptions::parseOptions()

}

#if SSL_OP_NO_SSLv2
#if defined(SSL_OP_NO_SSLv2)
// compliance with RFC 6176: Prohibiting Secure Sockets Layer (SSL) Version 2.0
op = op | SSL_OP_NO_SSLv2;
if (SSL_OP_NO_SSLv2)
op |= SSL_OP_NO_SSLv2;
#endif
parsedOptions = op;

Expand Down
82 changes: 82 additions & 0 deletions src/security/ServerOptions.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "anyp/PortCfg.h"
#include "base/Packable.h"
#include "cache_cf.h"
#include "error/SysErrorDetail.h"
#include "fatal.h"
#include "globals.h"
#include "security/ServerOptions.h"
Expand All @@ -19,6 +20,9 @@
#include "compat/openssl.h"
#include "ssl/support.h"

#if HAVE_OPENSSL_DECODER_H
#include <openssl/decoder.h>
#endif
#if HAVE_OPENSSL_ERR_H
#include <openssl/err.h>
#endif
Expand Down Expand Up @@ -352,11 +356,20 @@ Security::ServerOptions::loadDhParams()
if (dhParamsFile.isEmpty())
return;

// TODO: After loading and validating parameters, also validate that "the
// public and private components have the correct mathematical
// relationship". See EVP_PKEY_check().

#if USE_OPENSSL
#if OPENSSL_VERSION_MAJOR < 3
DH *dhp = nullptr;
if (FILE *in = fopen(dhParamsFile.c_str(), "r")) {
dhp = PEM_read_DHparams(in, nullptr, nullptr, nullptr);
fclose(in);
} else {
const auto xerrno = errno;
debugs(83, DBG_IMPORTANT, "WARNING: Failed to open '" << dhParamsFile << "'" << ReportSysError(xerrno));
return;
}

if (!dhp) {
Expand All @@ -374,7 +387,65 @@ Security::ServerOptions::loadDhParams()
}

parsedDhParams.resetWithoutLocking(dhp);

#else // OpenSSL 3.0+
const auto type = eecdhCurve.isEmpty() ? "DH" : "EC";

Ssl::ForgetErrors();
EVP_PKEY *rawPkey = nullptr;
using DecoderContext = std::unique_ptr<OSSL_DECODER_CTX, HardFun<void, OSSL_DECODER_CTX*, &OSSL_DECODER_CTX_free> >;
if (const DecoderContext dctx{OSSL_DECODER_CTX_new_for_pkey(&rawPkey, "PEM", nullptr, type, 0, nullptr, nullptr)}) {

// OpenSSL documentation is vague on this, but OpenSSL code and our
// tests suggest that rawPkey remains nil here while rawCtx keeps
// rawPkey _address_ for use by the decoder (see OSSL_DECODER_from_fp()
// below). Thus, we must not move *rawPkey into a smart pointer until
// decoding is over. For cleanup code simplicity, we assert nil rawPkey.
assert(!rawPkey);

if (OSSL_DECODER_CTX_get_num_decoders(dctx.get()) == 0) {
debugs(83, DBG_IMPORTANT, "WARNING: No suitable decoders found for " << type << " parameters" << Ssl::ReportAndForgetErrors);
return;
}

if (const auto in = fopen(dhParamsFile.c_str(), "r")) {
if (OSSL_DECODER_from_fp(dctx.get(), in)) {
assert(rawPkey);
const Security::DhePointer pkey(rawPkey);
// TODO: verify that the loaded parameters match the curve named in eecdhCurve

if (const Ssl::EVP_PKEY_CTX_Pointer pkeyCtx{EVP_PKEY_CTX_new_from_pkey(nullptr, pkey.get(), nullptr)}) {
switch (EVP_PKEY_param_check(pkeyCtx.get())) {
case 1: // success
parsedDhParams = pkey;
break;
case -2:
debugs(83, DBG_PARSE_NOTE(2), "WARNING: OpenSSL does not support " << type << " parameters check: " << dhParamsFile << Ssl::ReportAndForgetErrors);
break;
default:
debugs(83, DBG_IMPORTANT, "ERROR: Failed to verify " << type << " parameters in " << dhParamsFile << Ssl::ReportAndForgetErrors);
break;
}
} else {
// TODO: Reduce error reporting code duplication.
debugs(83, DBG_IMPORTANT, "ERROR: Cannot check " << type << " parameters in " << dhParamsFile << Ssl::ReportAndForgetErrors);
}
} else {
debugs(83, DBG_IMPORTANT, "WARNING: Failed to decode " << type << " parameters '" << dhParamsFile << "'" << Ssl::ReportAndForgetErrors);
EVP_PKEY_free(rawPkey); // probably still nil, but just in case
}
fclose(in);
} else {
const auto xerrno = errno;
debugs(83, DBG_IMPORTANT, "WARNING: Failed to open '" << dhParamsFile << "'" << ReportSysError(xerrno));
}

} else {
debugs(83, DBG_IMPORTANT, "WARNING: Unable to create decode context for " << type << " parameters" << Ssl::ReportAndForgetErrors);
return;
}
#endif
#endif // USE_OPENSSL
}

bool
Expand Down Expand Up @@ -454,12 +525,16 @@ Security::ServerOptions::updateContextEecdh(Security::ContextPointer &ctx)
debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve << ".");

#if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)

Ssl::ForgetErrors();

int nid = OBJ_sn2nid(eecdhCurve.c_str());
if (!nid) {
debugs(83, DBG_CRITICAL, "ERROR: Unknown EECDH curve '" << eecdhCurve << "'");
return;
}

#if OPENSSL_VERSION_MAJOR < 3
auto ecdh = EC_KEY_new_by_curve_name(nid);
if (!ecdh) {
const auto x = ERR_get_error();
Expand All @@ -473,6 +548,13 @@ Security::ServerOptions::updateContextEecdh(Security::ContextPointer &ctx)
}
EC_KEY_free(ecdh);

#else
// TODO: Support multiple group names via SSL_CTX_set1_groups_list().
if (!SSL_CTX_set1_groups(ctx.get(), &nid, 1)) {
debugs(83, DBG_CRITICAL, "ERROR: Unable to set Ephemeral ECDH: " << Ssl::ReportAndForgetErrors);
return;
}
#endif
#else
debugs(83, DBG_CRITICAL, "ERROR: EECDH is not available in this build." <<
" Please link against OpenSSL>=0.9.8 and ensure OPENSSL_NO_ECDH is not set.");
Expand Down
Loading

0 comments on commit 742236c

Please sign in to comment.