2 * Copyright (C) 1996-2020 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"
15 #include "security/ServerOptions.h"
16 #include "security/Session.h"
17 #include "SquidConfig.h"
19 #include "compat/openssl.h"
20 #include "ssl/support.h"
22 #if HAVE_OPENSSL_ERR_H
23 #include <openssl/err.h>
27 Security::ServerOptions
&
28 Security::ServerOptions::operator =(const Security::ServerOptions
&old
) {
30 Security::PeerOptions::operator =(old
);
31 clientCaFile
= old
.clientCaFile
;
33 dhParamsFile
= old
.dhParamsFile
;
34 eecdhCurve
= old
.eecdhCurve
;
35 parsedDhParams
= old
.parsedDhParams
;
37 if (auto *stk
= SSL_dup_CA_list(old
.clientCaStack
.get()))
38 clientCaStack
= Security::ServerOptions::X509_NAME_STACK_Pointer(stk
);
41 clientCaStack
= nullptr;
43 staticContextSessionId
= old
.staticContextSessionId
;
44 generateHostCertificates
= old
.generateHostCertificates
;
45 signingCa
= old
.signingCa
;
46 untrustedSigningCa
= old
.untrustedSigningCa
;
47 dynamicCertMemCacheSize
= old
.dynamicCertMemCacheSize
;
53 Security::ServerOptions::parse(const char *token
)
56 // config says just "ssl" or "tls" (or "tls-")
57 encryptTransport
= true;
61 // parse the server-only options
62 if (strncmp(token
, "clientca=", 9) == 0) {
63 clientCaFile
= SBuf(token
+ 9);
64 } else if (strncmp(token
, "dh=", 3) == 0) {
65 // clear any previous Diffi-Helman configuration
73 auto pos
= dh
.find(':');
74 if (pos
!= SBuf::npos
) { // tls-dh=eecdhCurve:dhParamsFile
75 eecdhCurve
= dh
.substr(0,pos
);
76 dhParamsFile
= dh
.substr(pos
+1);
77 } else { // tls-dh=dhParamsFile
79 // empty eecdhCurve means "do not use EECDH"
85 } else if (strncmp(token
, "dhparams=", 9) == 0) {
86 if (!eecdhCurve
.isEmpty()) {
87 debugs(83, DBG_PARSE_NOTE(1), "UPGRADE WARNING: EECDH settings in tls-dh= override dhparams=");
91 // backward compatibility for dhparams= configuration
98 } else if (strncmp(token
, "dynamic_cert_mem_cache_size=", 28) == 0) {
99 parseBytesOptionValue(&dynamicCertMemCacheSize
, "bytes", token
+ 28);
100 // XXX: parseBytesOptionValue() self_destruct()s on invalid values,
101 // probably making this comparison and misleading ERROR unnecessary.
102 if (dynamicCertMemCacheSize
== std::numeric_limits
<size_t>::max()) {
103 debugs(3, DBG_CRITICAL
, "ERROR: Cannot allocate memory for '" << token
<< "'. Using default of 4MB instead.");
104 dynamicCertMemCacheSize
= 4*1024*1024; // 4 MB
107 } else if (strcmp(token
, "generate-host-certificates") == 0) {
108 generateHostCertificates
= true;
109 } else if (strcmp(token
, "generate-host-certificates=on") == 0) {
110 generateHostCertificates
= true;
111 } else if (strcmp(token
, "generate-host-certificates=off") == 0) {
112 generateHostCertificates
= false;
114 } else if (strncmp(token
, "context=", 8) == 0) {
116 staticContextSessionId
= SBuf(token
+8);
117 // to hide its arguably sensitive value, do not print token in these debugs
118 if (staticContextSessionId
.length() > SSL_MAX_SSL_SESSION_ID_LENGTH
) {
119 debugs(83, DBG_CRITICAL
, "FATAL: Option 'context=' value is too long. Maximum " << SSL_MAX_SSL_SESSION_ID_LENGTH
<< " characters.");
123 debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT
), "WARNING: Option 'context=' requires --with-openssl. Ignoring.");
127 // parse generic TLS options
128 Security::PeerOptions::parse(token
);
133 Security::ServerOptions::dumpCfg(Packable
*p
, const char *pfx
) const
135 // dump out the generic TLS options
136 Security::PeerOptions::dumpCfg(p
, pfx
);
138 if (!encryptTransport
)
139 return; // no other settings are relevant
141 // dump the server-only options
143 p
->appendf(" %sdh=" SQUIDSBUFPH
, pfx
, SQUIDSBUFPRINT(dh
));
145 if (!generateHostCertificates
)
146 p
->appendf(" %sgenerate-host-certificates=off", pfx
);
148 if (dynamicCertMemCacheSize
!= 4*1024*1024) // 4MB default, no 'tls-' prefix
149 p
->appendf(" dynamic_cert_mem_cache_size=%" PRIuSIZE
"bytes", dynamicCertMemCacheSize
);
151 if (!staticContextSessionId
.isEmpty())
152 p
->appendf(" %scontext=" SQUIDSBUFPH
, pfx
, SQUIDSBUFPRINT(staticContextSessionId
));
155 Security::ContextPointer
156 Security::ServerOptions::createBlankContext() const
158 Security::ContextPointer ctx
;
162 SSL_CTX
*t
= SSL_CTX_new(TLS_server_method());
164 const auto x
= ERR_get_error();
165 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x
));
167 ctx
= convertContextFromRawPtr(t
);
170 // Initialize for X.509 certificate exchange
171 gnutls_certificate_credentials_t t
;
172 if (const int x
= gnutls_certificate_allocate_credentials(&t
)) {
173 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x
));
175 ctx
= convertContextFromRawPtr(t
);
178 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: No TLS library");
186 Security::ServerOptions::initServerContexts(AnyP::PortCfg
&port
)
188 const char *portType
= AnyP::ProtocolType_str
[port
.transport
.protocol
];
189 for (auto &keyData
: certs
) {
190 keyData
.loadFromFiles(port
, portType
);
193 if (generateHostCertificates
) {
194 createSigningContexts(port
);
197 if (!certs
.empty() && !createStaticServerContext(port
)) {
199 fatalf("%s_port %s initialization error", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
202 // if generate-host-certificates=off and certs is empty, no contexts may be created.
203 // features depending on contexts do their own checks and error messages later.
207 Security::ServerOptions::createStaticServerContext(AnyP::PortCfg
&port
)
209 updateTlsVersionLimits();
211 Security::ContextPointer
t(createBlankContext());
215 if (certs
.size() > 1) {
216 // NOTE: calling SSL_CTX_use_certificate() repeatedly _replaces_ the previous cert details.
217 // so we cannot use it and support multiple server certificates with OpenSSL.
218 debugs(83, DBG_CRITICAL
, "ERROR: OpenSSL does not support multiple server certificates. Ignoring addional cert= parameters.");
221 const auto &keys
= certs
.front();
223 if (!SSL_CTX_use_certificate(t
.get(), keys
.cert
.get())) {
224 const auto x
= ERR_get_error();
225 debugs(83, DBG_CRITICAL
, "ERROR: Failed to acquire TLS certificate '" << keys
.certFile
<< "': " << Security::ErrorString(x
));
229 if (!SSL_CTX_use_PrivateKey(t
.get(), keys
.pkey
.get())) {
230 const auto x
= ERR_get_error();
231 debugs(83, DBG_CRITICAL
, "ERROR: Failed to acquire TLS private key '" << keys
.privateKeyFile
<< "': " << Security::ErrorString(x
));
235 for (auto cert
: keys
.chain
) {
236 if (SSL_CTX_add_extra_chain_cert(t
.get(), cert
.get())) {
237 // increase the certificate lock
238 X509_up_ref(cert
.get());
240 const auto error
= ERR_get_error();
241 debugs(83, DBG_IMPORTANT
, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(error
));
246 for (auto &keys
: certs
) {
247 gnutls_x509_crt_t crt
= keys
.cert
.get();
248 gnutls_x509_privkey_t xkey
= keys
.pkey
.get();
249 const auto x
= gnutls_certificate_set_x509_key(t
.get(), &crt
, 1, xkey
);
250 if (x
!= GNUTLS_E_SUCCESS
) {
251 SBuf whichFile
= keys
.certFile
;
252 if (keys
.certFile
!= keys
.privateKeyFile
) {
253 whichFile
.appendf(" and ");
254 whichFile
.append(keys
.privateKeyFile
);
256 debugs(83, DBG_CRITICAL
, "ERROR: Failed to initialize server context with keys from " << whichFile
<< ": " << Security::ErrorString(x
));
259 // XXX: add cert chain to the context
263 if (!loadClientCaFile())
266 // by this point all config related files must be loaded
267 if (!updateContextConfig(t
)) {
268 debugs(83, DBG_CRITICAL
, "ERROR: Configuring static TLS context");
273 staticContext
= std::move(t
);
274 return bool(staticContext
);
278 Security::ServerOptions::createSigningContexts(const AnyP::PortCfg
&port
)
280 // For signing we do not have a pre-initialized context object. Instead
281 // contexts are generated as needed. This method initializes the cert
282 // and key pointers used to sign those contexts later.
284 signingCa
= certs
.front();
286 const char *portType
= AnyP::ProtocolType_str
[port
.transport
.protocol
];
287 if (!signingCa
.cert
) {
289 // XXX: we never actually checked that the cert is capable of signing!
290 fatalf("No valid signing certificate configured for %s_port %s", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
294 debugs(3, DBG_IMPORTANT
, "No TLS private key configured for " << portType
<< "_port " << port
.s
);
297 Ssl::generateUntrustedCert(untrustedSigningCa
.cert
, untrustedSigningCa
.pkey
, signingCa
.cert
, signingCa
.pkey
);
299 // TODO: implement for GnuTLS. Just a warning for now since generate is implicitly on for all crypto builds.
300 signingCa
.cert
.reset();
301 signingCa
.pkey
.reset();
302 debugs(83, DBG_CRITICAL
, "WARNING: Dynamic TLS certificate generation requires --with-openssl.");
305 debugs(83, DBG_CRITICAL
, "ERROR: Dynamic TLS certificate generation requires --with-openssl.");
309 if (!untrustedSigningCa
.cert
) {
311 fatalf("Unable to generate signing certificate for untrusted sites for %s_port %s", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
316 Security::ServerOptions::syncCaFiles()
318 // if caFiles is set, just use that
322 // otherwise fall back to clientca if it is defined
323 if (!clientCaFile
.isEmpty())
324 caFiles
.emplace_back(clientCaFile
);
327 /// load clientca= file (if any) into memory.
328 /// \retval true clientca is not set, or loaded successfully
329 /// \retval false unable to load the file, or not using OpenSSL
331 Security::ServerOptions::loadClientCaFile()
333 if (clientCaFile
.isEmpty())
337 auto *stk
= SSL_load_client_CA_file(clientCaFile
.c_str());
338 clientCaStack
= Security::ServerOptions::X509_NAME_STACK_Pointer(stk
);
340 if (!clientCaStack
) {
341 debugs(83, DBG_CRITICAL
, "FATAL: Unable to read client CAs from file: " << clientCaFile
);
344 return bool(clientCaStack
);
348 Security::ServerOptions::loadDhParams()
350 if (dhParamsFile
.isEmpty())
355 if (FILE *in
= fopen(dhParamsFile
.c_str(), "r")) {
356 dhp
= PEM_read_DHparams(in
, NULL
, NULL
, NULL
);
361 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to read DH parameters '" << dhParamsFile
<< "'");
366 if (DH_check(dhp
, &codes
) == 0) {
368 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to verify DH parameters '" << dhParamsFile
<< "' (" << std::hex
<< codes
<< ")");
374 parsedDhParams
.resetWithoutLocking(dhp
);
379 Security::ServerOptions::updateContextConfig(Security::ContextPointer
&ctx
)
381 updateContextOptions(ctx
);
382 updateContextSessionId(ctx
);
385 if (parsedFlags
& SSL_FLAG_NO_SESSION_REUSE
) {
386 SSL_CTX_set_session_cache_mode(ctx
.get(), SSL_SESS_CACHE_OFF
);
389 if (Config
.SSL
.unclean_shutdown
) {
390 debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation).");
391 SSL_CTX_set_quiet_shutdown(ctx
.get(), 1);
394 if (!sslCipher
.isEmpty()) {
395 debugs(83, 5, "Using cipher suite " << sslCipher
<< ".");
396 if (!SSL_CTX_set_cipher_list(ctx
.get(), sslCipher
.c_str())) {
397 auto ssl_error
= ERR_get_error();
398 debugs(83, DBG_CRITICAL
, "ERROR: Failed to set SSL cipher suite '" << sslCipher
<< "': " << Security::ErrorString(ssl_error
));
403 Ssl::MaybeSetupRsaCallback(ctx
);
406 updateContextEecdh(ctx
);
407 updateContextCa(ctx
);
408 updateContextClientCa(ctx
);
411 if (parsedFlags
& SSL_FLAG_DONT_VERIFY_DOMAIN
)
412 SSL_CTX_set_ex_data(ctx
.get(), ssl_ctx_ex_index_dont_verify_domain
, (void *) -1);
414 Security::SetSessionCacheCallbacks(ctx
);
420 Security::ServerOptions::updateContextClientCa(Security::ContextPointer
&ctx
)
425 if (STACK_OF(X509_NAME
) *clientca
= SSL_dup_CA_list(clientCaStack
.get())) {
426 SSL_CTX_set_client_CA_list(ctx
.get(), clientca
);
428 auto ssl_error
= ERR_get_error();
429 debugs(83, DBG_CRITICAL
, "ERROR: Failed to dupe the client CA list: " << Security::ErrorString(ssl_error
));
433 if (parsedFlags
& SSL_FLAG_DELAYED_AUTH
) {
434 debugs(83, 9, "Not requesting client certificates until acl processing requires one");
435 SSL_CTX_set_verify(ctx
.get(), SSL_VERIFY_NONE
, nullptr);
437 debugs(83, 9, "Requiring client certificates.");
438 Ssl::SetupVerifyCallback(ctx
);
441 updateContextCrl(ctx
);
442 updateContextTrust(ctx
);
445 debugs(83, 9, "Not requiring any client certificates");
446 SSL_CTX_set_verify(ctx
.get(), SSL_VERIFY_NONE
, NULL
);
452 Security::ServerOptions::updateContextEecdh(Security::ContextPointer
&ctx
)
454 // set Elliptic Curve details into the server context
455 if (!eecdhCurve
.isEmpty()) {
456 debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve
<< ".");
458 #if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
459 int nid
= OBJ_sn2nid(eecdhCurve
.c_str());
461 debugs(83, DBG_CRITICAL
, "ERROR: Unknown EECDH curve '" << eecdhCurve
<< "'");
465 auto ecdh
= EC_KEY_new_by_curve_name(nid
);
467 const auto x
= ERR_get_error();
468 debugs(83, DBG_CRITICAL
, "ERROR: Unable to configure Ephemeral ECDH: " << Security::ErrorString(x
));
472 if (!SSL_CTX_set_tmp_ecdh(ctx
.get(), ecdh
)) {
473 const auto x
= ERR_get_error();
474 debugs(83, DBG_CRITICAL
, "ERROR: Unable to set Ephemeral ECDH: " << Security::ErrorString(x
));
479 debugs(83, DBG_CRITICAL
, "ERROR: EECDH is not available in this build." <<
480 " Please link against OpenSSL>=0.9.8 and ensure OPENSSL_NO_ECDH is not set.");
484 // set DH parameters into the server context
486 if (parsedDhParams
) {
487 SSL_CTX_set_tmp_dh(ctx
.get(), parsedDhParams
.get());
493 Security::ServerOptions::updateContextSessionId(Security::ContextPointer
&ctx
)
496 if (!staticContextSessionId
.isEmpty())
497 SSL_CTX_set_session_id_context(ctx
.get(), reinterpret_cast<const unsigned char*>(staticContextSessionId
.rawContent()), staticContextSessionId
.length());