2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
10 #include "anyp/PortCfg.h"
11 #include "base/Packable.h"
13 #include "error/SysErrorDetail.h"
16 #include "security/ServerOptions.h"
17 #include "security/Session.h"
18 #include "SquidConfig.h"
20 #include "compat/openssl.h"
21 #include "ssl/support.h"
23 #if HAVE_OPENSSL_DECODER_H
24 #include <openssl/decoder.h>
26 #if HAVE_OPENSSL_ERR_H
27 #include <openssl/err.h>
33 Security::ServerOptions
&
34 Security::ServerOptions::operator =(const Security::ServerOptions
&old
) {
36 Security::PeerOptions::operator =(old
);
37 clientCaFile
= old
.clientCaFile
;
39 dhParamsFile
= old
.dhParamsFile
;
40 eecdhCurve
= old
.eecdhCurve
;
41 parsedDhParams
= old
.parsedDhParams
;
43 if (auto *stk
= SSL_dup_CA_list(old
.clientCaStack
.get()))
44 clientCaStack
= Security::ServerOptions::X509_NAME_STACK_Pointer(stk
);
47 clientCaStack
= nullptr;
49 staticContextSessionId
= old
.staticContextSessionId
;
50 generateHostCertificates
= old
.generateHostCertificates
;
51 signingCa
= old
.signingCa
;
52 untrustedSigningCa
= old
.untrustedSigningCa
;
53 dynamicCertMemCacheSize
= old
.dynamicCertMemCacheSize
;
59 Security::ServerOptions::parse(const char *token
)
62 // config says just "ssl" or "tls" (or "tls-")
63 encryptTransport
= true;
67 // parse the server-only options
68 if (strncmp(token
, "clientca=", 9) == 0) {
69 clientCaFile
= SBuf(token
+ 9);
70 } else if (strncmp(token
, "dh=", 3) == 0) {
71 // clear any previous Diffi-Helman configuration
79 auto pos
= dh
.find(':');
80 if (pos
!= SBuf::npos
) { // tls-dh=eecdhCurve:dhParamsFile
81 eecdhCurve
= dh
.substr(0,pos
);
82 dhParamsFile
= dh
.substr(pos
+1);
83 } else { // tls-dh=dhParamsFile
85 // empty eecdhCurve means "do not use EECDH"
91 } else if (strncmp(token
, "dhparams=", 9) == 0) {
92 if (!eecdhCurve
.isEmpty()) {
93 debugs(83, DBG_PARSE_NOTE(1), "WARNING: UPGRADE: EECDH settings in tls-dh= override dhparams=");
97 // backward compatibility for dhparams= configuration
104 } else if (strncmp(token
, "dynamic_cert_mem_cache_size=", 28) == 0) {
105 parseBytesOptionValue(&dynamicCertMemCacheSize
, "bytes", token
+ 28);
106 // XXX: parseBytesOptionValue() self_destruct()s on invalid values,
107 // probably making this comparison and misleading ERROR unnecessary.
108 if (dynamicCertMemCacheSize
== std::numeric_limits
<size_t>::max()) {
109 debugs(3, DBG_CRITICAL
, "ERROR: Cannot allocate memory for '" << token
<< "'. Using default of 4MB instead.");
110 dynamicCertMemCacheSize
= 4*1024*1024; // 4 MB
113 } else if (strcmp(token
, "generate-host-certificates") == 0) {
114 generateHostCertificates
= true;
115 } else if (strcmp(token
, "generate-host-certificates=on") == 0) {
116 generateHostCertificates
= true;
117 } else if (strcmp(token
, "generate-host-certificates=off") == 0) {
118 generateHostCertificates
= false;
120 } else if (strncmp(token
, "context=", 8) == 0) {
122 staticContextSessionId
= SBuf(token
+8);
123 // to hide its arguably sensitive value, do not print token in these debugs
124 if (staticContextSessionId
.length() > SSL_MAX_SSL_SESSION_ID_LENGTH
) {
125 debugs(83, DBG_CRITICAL
, "FATAL: Option 'context=' value is too long. Maximum " << SSL_MAX_SSL_SESSION_ID_LENGTH
<< " characters.");
129 debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT
), "WARNING: Option 'context=' requires --with-openssl. Ignoring.");
133 // parse generic TLS options
134 Security::PeerOptions::parse(token
);
139 Security::ServerOptions::dumpCfg(Packable
*p
, const char *pfx
) const
141 // dump out the generic TLS options
142 Security::PeerOptions::dumpCfg(p
, pfx
);
144 if (!encryptTransport
)
145 return; // no other settings are relevant
147 // dump the server-only options
149 p
->appendf(" %sdh=" SQUIDSBUFPH
, pfx
, SQUIDSBUFPRINT(dh
));
151 if (!generateHostCertificates
)
152 p
->appendf(" %sgenerate-host-certificates=off", pfx
);
154 if (dynamicCertMemCacheSize
!= 4*1024*1024) // 4MB default, no 'tls-' prefix
155 p
->appendf(" dynamic_cert_mem_cache_size=%zubytes", dynamicCertMemCacheSize
);
157 if (!staticContextSessionId
.isEmpty())
158 p
->appendf(" %scontext=" SQUIDSBUFPH
, pfx
, SQUIDSBUFPRINT(staticContextSessionId
));
161 Security::ContextPointer
162 Security::ServerOptions::createBlankContext() const
164 Security::ContextPointer ctx
;
168 SSL_CTX
*t
= SSL_CTX_new(TLS_server_method());
170 const auto x
= ERR_get_error();
171 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x
));
173 ctx
= convertContextFromRawPtr(t
);
176 // Initialize for X.509 certificate exchange
177 gnutls_certificate_credentials_t t
;
178 if (const auto x
= gnutls_certificate_allocate_credentials(&t
)) {
179 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x
));
181 ctx
= convertContextFromRawPtr(t
);
184 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: No TLS library");
192 Security::ServerOptions::initServerContexts(AnyP::PortCfg
&port
)
194 const char *portType
= AnyP::ProtocolType_str
[port
.transport
.protocol
];
195 for (auto &keyData
: certs
) {
196 keyData
.loadFromFiles(port
, portType
);
199 if (generateHostCertificates
) {
200 createSigningContexts(port
);
203 if (!certs
.empty() && !createStaticServerContext(port
)) {
205 fatalf("%s_port %s initialization error", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
208 // if generate-host-certificates=off and certs is empty, no contexts may be created.
209 // features depending on contexts do their own checks and error messages later.
213 Security::ServerOptions::createStaticServerContext(AnyP::PortCfg
&)
215 updateTlsVersionLimits();
217 Security::ContextPointer
t(createBlankContext());
221 if (certs
.size() > 1) {
222 // NOTE: calling SSL_CTX_use_certificate() repeatedly _replaces_ the previous cert details.
223 // so we cannot use it and support multiple server certificates with OpenSSL.
224 debugs(83, DBG_CRITICAL
, "ERROR: OpenSSL does not support multiple server certificates. Ignoring additional cert= parameters.");
227 const auto &keys
= certs
.front();
229 if (!SSL_CTX_use_certificate(t
.get(), keys
.cert
.get())) {
230 const auto x
= ERR_get_error();
231 debugs(83, DBG_CRITICAL
, "ERROR: Failed to acquire TLS certificate '" << keys
.certFile
<< "': " << Security::ErrorString(x
));
235 if (!SSL_CTX_use_PrivateKey(t
.get(), keys
.pkey
.get())) {
236 const auto x
= ERR_get_error();
237 debugs(83, DBG_CRITICAL
, "ERROR: Failed to acquire TLS private key '" << keys
.privateKeyFile
<< "': " << Security::ErrorString(x
));
241 for (auto cert
: keys
.chain
) {
242 if (SSL_CTX_add_extra_chain_cert(t
.get(), cert
.get())) {
243 // increase the certificate lock
244 X509_up_ref(cert
.get());
246 const auto error
= ERR_get_error();
247 debugs(83, DBG_IMPORTANT
, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(error
));
252 for (auto &keys
: certs
) {
253 gnutls_x509_crt_t crt
= keys
.cert
.get();
254 gnutls_x509_privkey_t xkey
= keys
.pkey
.get();
255 const auto x
= gnutls_certificate_set_x509_key(t
.get(), &crt
, 1, xkey
);
256 if (x
!= GNUTLS_E_SUCCESS
) {
257 SBuf whichFile
= keys
.certFile
;
258 if (keys
.certFile
!= keys
.privateKeyFile
) {
259 whichFile
.appendf(" and ");
260 whichFile
.append(keys
.privateKeyFile
);
262 debugs(83, DBG_CRITICAL
, "ERROR: Failed to initialize server context with keys from " << whichFile
<< ": " << Security::ErrorString(x
));
265 // XXX: add cert chain to the context
269 if (!loadClientCaFile())
272 // by this point all config related files must be loaded
273 if (!updateContextConfig(t
)) {
274 debugs(83, DBG_CRITICAL
, "ERROR: Configuring static TLS context");
279 staticContext
= std::move(t
);
280 return bool(staticContext
);
284 Security::ServerOptions::createSigningContexts(const AnyP::PortCfg
&port
)
286 // For signing we do not have a pre-initialized context object. Instead
287 // contexts are generated as needed. This method initializes the cert
288 // and key pointers used to sign those contexts later.
290 signingCa
= certs
.front();
292 const char *portType
= AnyP::ProtocolType_str
[port
.transport
.protocol
];
293 if (!signingCa
.cert
) {
295 // XXX: we never actually checked that the cert is capable of signing!
296 fatalf("No valid signing certificate configured for %s_port %s", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
300 debugs(3, DBG_IMPORTANT
, "No TLS private key configured for " << portType
<< "_port " << port
.s
);
303 Ssl::generateUntrustedCert(untrustedSigningCa
.cert
, untrustedSigningCa
.pkey
, signingCa
.cert
, signingCa
.pkey
);
305 // TODO: implement for GnuTLS. Just a warning for now since generate is implicitly on for all crypto builds.
306 signingCa
.cert
.reset();
307 signingCa
.pkey
.reset();
308 debugs(83, DBG_CRITICAL
, "WARNING: Dynamic TLS certificate generation requires --with-openssl.");
311 debugs(83, DBG_CRITICAL
, "ERROR: Dynamic TLS certificate generation requires --with-openssl.");
315 if (!untrustedSigningCa
.cert
) {
317 fatalf("Unable to generate signing certificate for untrusted sites for %s_port %s", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
322 Security::ServerOptions::syncCaFiles()
324 // if caFiles is set, just use that
328 // otherwise fall back to clientca if it is defined
329 if (!clientCaFile
.isEmpty())
330 caFiles
.emplace_back(clientCaFile
);
333 /// load clientca= file (if any) into memory.
334 /// \retval true clientca is not set, or loaded successfully
335 /// \retval false unable to load the file, or not using OpenSSL
337 Security::ServerOptions::loadClientCaFile()
339 if (clientCaFile
.isEmpty())
343 auto *stk
= SSL_load_client_CA_file(clientCaFile
.c_str());
344 clientCaStack
= Security::ServerOptions::X509_NAME_STACK_Pointer(stk
);
346 if (!clientCaStack
) {
347 debugs(83, DBG_CRITICAL
, "FATAL: Unable to read client CAs from file: " << clientCaFile
);
350 return bool(clientCaStack
);
354 Security::ServerOptions::loadDhParams()
356 if (dhParamsFile
.isEmpty())
359 // TODO: After loading and validating parameters, also validate that "the
360 // public and private components have the correct mathematical
361 // relationship". See EVP_PKEY_check().
364 #if OPENSSL_VERSION_MAJOR < 3
366 if (FILE *in
= fopen(dhParamsFile
.c_str(), "r")) {
367 dhp
= PEM_read_DHparams(in
, nullptr, nullptr, nullptr);
370 const auto xerrno
= errno
;
371 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to open '" << dhParamsFile
<< "'" << ReportSysError(xerrno
));
376 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to read DH parameters '" << dhParamsFile
<< "'");
381 if (DH_check(dhp
, &codes
) == 0) {
383 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to verify DH parameters '" << dhParamsFile
<< "' (" << std::hex
<< codes
<< ")");
389 parsedDhParams
.resetWithoutLocking(dhp
);
391 #else // OpenSSL 3.0+
392 const auto type
= eecdhCurve
.isEmpty() ? "DH" : "EC";
395 EVP_PKEY
*rawPkey
= nullptr;
396 using DecoderContext
= std::unique_ptr
<OSSL_DECODER_CTX
, HardFun
<void, OSSL_DECODER_CTX
*, &OSSL_DECODER_CTX_free
> >;
397 if (const DecoderContext dctx
{OSSL_DECODER_CTX_new_for_pkey(&rawPkey
, "PEM", nullptr, type
, 0, nullptr, nullptr)}) {
399 // OpenSSL documentation is vague on this, but OpenSSL code and our
400 // tests suggest that rawPkey remains nil here while rawCtx keeps
401 // rawPkey _address_ for use by the decoder (see OSSL_DECODER_from_fp()
402 // below). Thus, we must not move *rawPkey into a smart pointer until
403 // decoding is over. For cleanup code simplicity, we assert nil rawPkey.
406 if (OSSL_DECODER_CTX_get_num_decoders(dctx
.get()) == 0) {
407 debugs(83, DBG_IMPORTANT
, "WARNING: No suitable decoders found for " << type
<< " parameters" << Ssl::ReportAndForgetErrors
);
411 if (const auto in
= fopen(dhParamsFile
.c_str(), "r")) {
412 if (OSSL_DECODER_from_fp(dctx
.get(), in
)) {
414 const Security::DhePointer
pkey(rawPkey
);
415 // TODO: verify that the loaded parameters match the curve named in eecdhCurve
417 if (const Ssl::EVP_PKEY_CTX_Pointer pkeyCtx
{EVP_PKEY_CTX_new_from_pkey(nullptr, pkey
.get(), nullptr)}) {
418 switch (EVP_PKEY_param_check(pkeyCtx
.get())) {
420 parsedDhParams
= pkey
;
423 debugs(83, DBG_PARSE_NOTE(2), "WARNING: OpenSSL does not support " << type
<< " parameters check: " << dhParamsFile
<< Ssl::ReportAndForgetErrors
);
426 debugs(83, DBG_IMPORTANT
, "ERROR: Failed to verify " << type
<< " parameters in " << dhParamsFile
<< Ssl::ReportAndForgetErrors
);
430 // TODO: Reduce error reporting code duplication.
431 debugs(83, DBG_IMPORTANT
, "ERROR: Cannot check " << type
<< " parameters in " << dhParamsFile
<< Ssl::ReportAndForgetErrors
);
434 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to decode " << type
<< " parameters '" << dhParamsFile
<< "'" << Ssl::ReportAndForgetErrors
);
435 EVP_PKEY_free(rawPkey
); // probably still nil, but just in case
439 const auto xerrno
= errno
;
440 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to open '" << dhParamsFile
<< "'" << ReportSysError(xerrno
));
444 debugs(83, DBG_IMPORTANT
, "WARNING: Unable to create decode context for " << type
<< " parameters" << Ssl::ReportAndForgetErrors
);
448 #endif // USE_OPENSSL
452 Security::ServerOptions::updateContextConfig(Security::ContextPointer
&ctx
)
454 updateContextOptions(ctx
);
455 updateContextSessionId(ctx
);
458 if (parsedFlags
& SSL_FLAG_NO_SESSION_REUSE
) {
459 SSL_CTX_set_session_cache_mode(ctx
.get(), SSL_SESS_CACHE_OFF
);
462 if (Config
.SSL
.unclean_shutdown
) {
463 debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation).");
464 SSL_CTX_set_quiet_shutdown(ctx
.get(), 1);
467 if (!sslCipher
.isEmpty()) {
468 debugs(83, 5, "Using cipher suite " << sslCipher
<< ".");
469 if (!SSL_CTX_set_cipher_list(ctx
.get(), sslCipher
.c_str())) {
470 auto ssl_error
= ERR_get_error();
471 debugs(83, DBG_CRITICAL
, "ERROR: Failed to set SSL cipher suite '" << sslCipher
<< "': " << Security::ErrorString(ssl_error
));
476 Ssl::MaybeSetupRsaCallback(ctx
);
479 updateContextEecdh(ctx
);
480 updateContextCa(ctx
);
481 updateContextClientCa(ctx
);
484 SSL_CTX_set_mode(ctx
.get(), SSL_MODE_NO_AUTO_CHAIN
);
485 if (parsedFlags
& SSL_FLAG_DONT_VERIFY_DOMAIN
)
486 SSL_CTX_set_ex_data(ctx
.get(), ssl_ctx_ex_index_dont_verify_domain
, (void *) -1);
488 Security::SetSessionCacheCallbacks(ctx
);
494 Security::ServerOptions::updateContextClientCa(Security::ContextPointer
&ctx
)
499 if (STACK_OF(X509_NAME
) *clientca
= SSL_dup_CA_list(clientCaStack
.get())) {
500 SSL_CTX_set_client_CA_list(ctx
.get(), clientca
);
502 auto ssl_error
= ERR_get_error();
503 debugs(83, DBG_CRITICAL
, "ERROR: Failed to dupe the client CA list: " << Security::ErrorString(ssl_error
));
507 Ssl::ConfigurePeerVerification(ctx
, parsedFlags
);
509 updateContextCrl(ctx
);
510 updateContextTrust(ctx
);
513 Ssl::DisablePeerVerification(ctx
);
521 Security::ServerOptions::updateContextEecdh(Security::ContextPointer
&ctx
)
523 // set Elliptic Curve details into the server context
524 if (!eecdhCurve
.isEmpty()) {
525 debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve
<< ".");
527 #if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
531 int nid
= OBJ_sn2nid(eecdhCurve
.c_str());
533 debugs(83, DBG_CRITICAL
, "ERROR: Unknown EECDH curve '" << eecdhCurve
<< "'");
537 #if OPENSSL_VERSION_MAJOR < 3
538 auto ecdh
= EC_KEY_new_by_curve_name(nid
);
540 const auto x
= ERR_get_error();
541 debugs(83, DBG_CRITICAL
, "ERROR: Unable to configure Ephemeral ECDH: " << Security::ErrorString(x
));
545 if (!SSL_CTX_set_tmp_ecdh(ctx
.get(), ecdh
)) {
546 const auto x
= ERR_get_error();
547 debugs(83, DBG_CRITICAL
, "ERROR: Unable to set Ephemeral ECDH: " << Security::ErrorString(x
));
552 // TODO: Support multiple group names via SSL_CTX_set1_groups_list().
553 if (!SSL_CTX_set1_groups(ctx
.get(), &nid
, 1)) {
554 debugs(83, DBG_CRITICAL
, "ERROR: Unable to set Ephemeral ECDH: " << Ssl::ReportAndForgetErrors
);
559 debugs(83, DBG_CRITICAL
, "ERROR: EECDH is not available in this build." <<
560 " Please link against OpenSSL>=0.9.8 and ensure OPENSSL_NO_ECDH is not set.");
565 // set DH parameters into the server context
567 if (parsedDhParams
) {
568 SSL_CTX_set_tmp_dh(ctx
.get(), parsedDhParams
.get());
574 Security::ServerOptions::updateContextSessionId(Security::ContextPointer
&ctx
)
577 if (!staticContextSessionId
.isEmpty())
578 SSL_CTX_set_session_id_context(ctx
.get(), reinterpret_cast<const unsigned char*>(staticContextSessionId
.rawContent()), staticContextSessionId
.length());