2 * Copyright (C) 1996-2021 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>
29 Security::ServerOptions
&
30 Security::ServerOptions::operator =(const Security::ServerOptions
&old
) {
32 Security::PeerOptions::operator =(old
);
33 clientCaFile
= old
.clientCaFile
;
35 dhParamsFile
= old
.dhParamsFile
;
36 eecdhCurve
= old
.eecdhCurve
;
37 parsedDhParams
= old
.parsedDhParams
;
39 if (auto *stk
= SSL_dup_CA_list(old
.clientCaStack
.get()))
40 clientCaStack
= Security::ServerOptions::X509_NAME_STACK_Pointer(stk
);
43 clientCaStack
= nullptr;
45 staticContextSessionId
= old
.staticContextSessionId
;
46 generateHostCertificates
= old
.generateHostCertificates
;
47 signingCa
= old
.signingCa
;
48 untrustedSigningCa
= old
.untrustedSigningCa
;
49 dynamicCertMemCacheSize
= old
.dynamicCertMemCacheSize
;
55 Security::ServerOptions::parse(const char *token
)
58 // config says just "ssl" or "tls" (or "tls-")
59 encryptTransport
= true;
63 // parse the server-only options
64 if (strncmp(token
, "clientca=", 9) == 0) {
65 clientCaFile
= SBuf(token
+ 9);
66 } else if (strncmp(token
, "dh=", 3) == 0) {
67 // clear any previous Diffi-Helman configuration
75 auto pos
= dh
.find(':');
76 if (pos
!= SBuf::npos
) { // tls-dh=eecdhCurve:dhParamsFile
77 eecdhCurve
= dh
.substr(0,pos
);
78 dhParamsFile
= dh
.substr(pos
+1);
79 } else { // tls-dh=dhParamsFile
81 // empty eecdhCurve means "do not use EECDH"
87 } else if (strncmp(token
, "dhparams=", 9) == 0) {
88 if (!eecdhCurve
.isEmpty()) {
89 debugs(83, DBG_PARSE_NOTE(1), "UPGRADE WARNING: EECDH settings in tls-dh= override dhparams=");
93 // backward compatibility for dhparams= configuration
100 } else if (strncmp(token
, "dynamic_cert_mem_cache_size=", 28) == 0) {
101 parseBytesOptionValue(&dynamicCertMemCacheSize
, "bytes", token
+ 28);
102 // XXX: parseBytesOptionValue() self_destruct()s on invalid values,
103 // probably making this comparison and misleading ERROR unnecessary.
104 if (dynamicCertMemCacheSize
== std::numeric_limits
<size_t>::max()) {
105 debugs(3, DBG_CRITICAL
, "ERROR: Cannot allocate memory for '" << token
<< "'. Using default of 4MB instead.");
106 dynamicCertMemCacheSize
= 4*1024*1024; // 4 MB
109 } else if (strcmp(token
, "generate-host-certificates") == 0) {
110 generateHostCertificates
= true;
111 } else if (strcmp(token
, "generate-host-certificates=on") == 0) {
112 generateHostCertificates
= true;
113 } else if (strcmp(token
, "generate-host-certificates=off") == 0) {
114 generateHostCertificates
= false;
116 } else if (strncmp(token
, "context=", 8) == 0) {
118 staticContextSessionId
= SBuf(token
+8);
119 // to hide its arguably sensitive value, do not print token in these debugs
120 if (staticContextSessionId
.length() > SSL_MAX_SSL_SESSION_ID_LENGTH
) {
121 debugs(83, DBG_CRITICAL
, "FATAL: Option 'context=' value is too long. Maximum " << SSL_MAX_SSL_SESSION_ID_LENGTH
<< " characters.");
125 debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT
), "WARNING: Option 'context=' requires --with-openssl. Ignoring.");
129 // parse generic TLS options
130 Security::PeerOptions::parse(token
);
135 Security::ServerOptions::dumpCfg(Packable
*p
, const char *pfx
) const
137 // dump out the generic TLS options
138 Security::PeerOptions::dumpCfg(p
, pfx
);
140 if (!encryptTransport
)
141 return; // no other settings are relevant
143 // dump the server-only options
145 p
->appendf(" %sdh=" SQUIDSBUFPH
, pfx
, SQUIDSBUFPRINT(dh
));
147 if (!generateHostCertificates
)
148 p
->appendf(" %sgenerate-host-certificates=off", pfx
);
150 if (dynamicCertMemCacheSize
!= 4*1024*1024) // 4MB default, no 'tls-' prefix
151 p
->appendf(" dynamic_cert_mem_cache_size=%" PRIuSIZE
"bytes", dynamicCertMemCacheSize
);
153 if (!staticContextSessionId
.isEmpty())
154 p
->appendf(" %scontext=" SQUIDSBUFPH
, pfx
, SQUIDSBUFPRINT(staticContextSessionId
));
157 Security::ContextPointer
158 Security::ServerOptions::createBlankContext() const
160 Security::ContextPointer ctx
;
164 SSL_CTX
*t
= SSL_CTX_new(TLS_server_method());
166 const auto x
= ERR_get_error();
167 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x
));
169 ctx
= convertContextFromRawPtr(t
);
172 // Initialize for X.509 certificate exchange
173 gnutls_certificate_credentials_t t
;
174 if (const auto x
= gnutls_certificate_allocate_credentials(&t
)) {
175 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x
));
177 ctx
= convertContextFromRawPtr(t
);
180 debugs(83, DBG_CRITICAL
, "ERROR: Failed to allocate TLS server context: No TLS library");
188 Security::ServerOptions::initServerContexts(AnyP::PortCfg
&port
)
190 const char *portType
= AnyP::ProtocolType_str
[port
.transport
.protocol
];
191 for (auto &keyData
: certs
) {
192 keyData
.loadFromFiles(port
, portType
);
195 if (generateHostCertificates
) {
196 createSigningContexts(port
);
199 if (!certs
.empty() && !createStaticServerContext(port
)) {
201 fatalf("%s_port %s initialization error", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
204 // if generate-host-certificates=off and certs is empty, no contexts may be created.
205 // features depending on contexts do their own checks and error messages later.
209 Security::ServerOptions::createStaticServerContext(AnyP::PortCfg
&port
)
211 updateTlsVersionLimits();
213 Security::ContextPointer
t(createBlankContext());
217 if (certs
.size() > 1) {
218 // NOTE: calling SSL_CTX_use_certificate() repeatedly _replaces_ the previous cert details.
219 // so we cannot use it and support multiple server certificates with OpenSSL.
220 debugs(83, DBG_CRITICAL
, "ERROR: OpenSSL does not support multiple server certificates. Ignoring additional cert= parameters.");
223 const auto &keys
= certs
.front();
225 if (!SSL_CTX_use_certificate(t
.get(), keys
.cert
.get())) {
226 const auto x
= ERR_get_error();
227 debugs(83, DBG_CRITICAL
, "ERROR: Failed to acquire TLS certificate '" << keys
.certFile
<< "': " << Security::ErrorString(x
));
231 if (!SSL_CTX_use_PrivateKey(t
.get(), keys
.pkey
.get())) {
232 const auto x
= ERR_get_error();
233 debugs(83, DBG_CRITICAL
, "ERROR: Failed to acquire TLS private key '" << keys
.privateKeyFile
<< "': " << Security::ErrorString(x
));
237 for (auto cert
: keys
.chain
) {
238 if (SSL_CTX_add_extra_chain_cert(t
.get(), cert
.get())) {
239 // increase the certificate lock
240 X509_up_ref(cert
.get());
242 const auto error
= ERR_get_error();
243 debugs(83, DBG_IMPORTANT
, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(error
));
248 for (auto &keys
: certs
) {
249 gnutls_x509_crt_t crt
= keys
.cert
.get();
250 gnutls_x509_privkey_t xkey
= keys
.pkey
.get();
251 const auto x
= gnutls_certificate_set_x509_key(t
.get(), &crt
, 1, xkey
);
252 if (x
!= GNUTLS_E_SUCCESS
) {
253 SBuf whichFile
= keys
.certFile
;
254 if (keys
.certFile
!= keys
.privateKeyFile
) {
255 whichFile
.appendf(" and ");
256 whichFile
.append(keys
.privateKeyFile
);
258 debugs(83, DBG_CRITICAL
, "ERROR: Failed to initialize server context with keys from " << whichFile
<< ": " << Security::ErrorString(x
));
261 // XXX: add cert chain to the context
265 if (!loadClientCaFile())
268 // by this point all config related files must be loaded
269 if (!updateContextConfig(t
)) {
270 debugs(83, DBG_CRITICAL
, "ERROR: Configuring static TLS context");
275 staticContext
= std::move(t
);
276 return bool(staticContext
);
280 Security::ServerOptions::createSigningContexts(const AnyP::PortCfg
&port
)
282 // For signing we do not have a pre-initialized context object. Instead
283 // contexts are generated as needed. This method initializes the cert
284 // and key pointers used to sign those contexts later.
286 signingCa
= certs
.front();
288 const char *portType
= AnyP::ProtocolType_str
[port
.transport
.protocol
];
289 if (!signingCa
.cert
) {
291 // XXX: we never actually checked that the cert is capable of signing!
292 fatalf("No valid signing certificate configured for %s_port %s", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
296 debugs(3, DBG_IMPORTANT
, "No TLS private key configured for " << portType
<< "_port " << port
.s
);
299 Ssl::generateUntrustedCert(untrustedSigningCa
.cert
, untrustedSigningCa
.pkey
, signingCa
.cert
, signingCa
.pkey
);
301 // TODO: implement for GnuTLS. Just a warning for now since generate is implicitly on for all crypto builds.
302 signingCa
.cert
.reset();
303 signingCa
.pkey
.reset();
304 debugs(83, DBG_CRITICAL
, "WARNING: Dynamic TLS certificate generation requires --with-openssl.");
307 debugs(83, DBG_CRITICAL
, "ERROR: Dynamic TLS certificate generation requires --with-openssl.");
311 if (!untrustedSigningCa
.cert
) {
313 fatalf("Unable to generate signing certificate for untrusted sites for %s_port %s", portType
, port
.s
.toUrl(buf
, sizeof(buf
)));
318 Security::ServerOptions::syncCaFiles()
320 // if caFiles is set, just use that
324 // otherwise fall back to clientca if it is defined
325 if (!clientCaFile
.isEmpty())
326 caFiles
.emplace_back(clientCaFile
);
329 /// load clientca= file (if any) into memory.
330 /// \retval true clientca is not set, or loaded successfully
331 /// \retval false unable to load the file, or not using OpenSSL
333 Security::ServerOptions::loadClientCaFile()
335 if (clientCaFile
.isEmpty())
339 auto *stk
= SSL_load_client_CA_file(clientCaFile
.c_str());
340 clientCaStack
= Security::ServerOptions::X509_NAME_STACK_Pointer(stk
);
342 if (!clientCaStack
) {
343 debugs(83, DBG_CRITICAL
, "FATAL: Unable to read client CAs from file: " << clientCaFile
);
346 return bool(clientCaStack
);
350 Security::ServerOptions::loadDhParams()
352 if (dhParamsFile
.isEmpty())
357 if (FILE *in
= fopen(dhParamsFile
.c_str(), "r")) {
358 dhp
= PEM_read_DHparams(in
, NULL
, NULL
, NULL
);
363 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to read DH parameters '" << dhParamsFile
<< "'");
368 if (DH_check(dhp
, &codes
) == 0) {
370 debugs(83, DBG_IMPORTANT
, "WARNING: Failed to verify DH parameters '" << dhParamsFile
<< "' (" << std::hex
<< codes
<< ")");
376 parsedDhParams
.resetWithoutLocking(dhp
);
381 Security::ServerOptions::updateContextConfig(Security::ContextPointer
&ctx
)
383 updateContextOptions(ctx
);
384 updateContextSessionId(ctx
);
387 if (parsedFlags
& SSL_FLAG_NO_SESSION_REUSE
) {
388 SSL_CTX_set_session_cache_mode(ctx
.get(), SSL_SESS_CACHE_OFF
);
391 if (Config
.SSL
.unclean_shutdown
) {
392 debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation).");
393 SSL_CTX_set_quiet_shutdown(ctx
.get(), 1);
396 if (!sslCipher
.isEmpty()) {
397 debugs(83, 5, "Using cipher suite " << sslCipher
<< ".");
398 if (!SSL_CTX_set_cipher_list(ctx
.get(), sslCipher
.c_str())) {
399 auto ssl_error
= ERR_get_error();
400 debugs(83, DBG_CRITICAL
, "ERROR: Failed to set SSL cipher suite '" << sslCipher
<< "': " << Security::ErrorString(ssl_error
));
405 Ssl::MaybeSetupRsaCallback(ctx
);
408 updateContextEecdh(ctx
);
409 updateContextCa(ctx
);
410 updateContextClientCa(ctx
);
413 if (parsedFlags
& SSL_FLAG_DONT_VERIFY_DOMAIN
)
414 SSL_CTX_set_ex_data(ctx
.get(), ssl_ctx_ex_index_dont_verify_domain
, (void *) -1);
416 Security::SetSessionCacheCallbacks(ctx
);
422 Security::ServerOptions::updateContextClientCa(Security::ContextPointer
&ctx
)
427 if (STACK_OF(X509_NAME
) *clientca
= SSL_dup_CA_list(clientCaStack
.get())) {
428 SSL_CTX_set_client_CA_list(ctx
.get(), clientca
);
430 auto ssl_error
= ERR_get_error();
431 debugs(83, DBG_CRITICAL
, "ERROR: Failed to dupe the client CA list: " << Security::ErrorString(ssl_error
));
435 Ssl::ConfigurePeerVerification(ctx
, parsedFlags
);
437 updateContextCrl(ctx
);
438 updateContextTrust(ctx
);
441 Ssl::DisablePeerVerification(ctx
);
447 Security::ServerOptions::updateContextEecdh(Security::ContextPointer
&ctx
)
449 // set Elliptic Curve details into the server context
450 if (!eecdhCurve
.isEmpty()) {
451 debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve
<< ".");
453 #if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
454 int nid
= OBJ_sn2nid(eecdhCurve
.c_str());
456 debugs(83, DBG_CRITICAL
, "ERROR: Unknown EECDH curve '" << eecdhCurve
<< "'");
460 auto ecdh
= EC_KEY_new_by_curve_name(nid
);
462 const auto x
= ERR_get_error();
463 debugs(83, DBG_CRITICAL
, "ERROR: Unable to configure Ephemeral ECDH: " << Security::ErrorString(x
));
467 if (!SSL_CTX_set_tmp_ecdh(ctx
.get(), ecdh
)) {
468 const auto x
= ERR_get_error();
469 debugs(83, DBG_CRITICAL
, "ERROR: Unable to set Ephemeral ECDH: " << Security::ErrorString(x
));
474 debugs(83, DBG_CRITICAL
, "ERROR: EECDH is not available in this build." <<
475 " Please link against OpenSSL>=0.9.8 and ensure OPENSSL_NO_ECDH is not set.");
479 // set DH parameters into the server context
481 if (parsedDhParams
) {
482 SSL_CTX_set_tmp_dh(ctx
.get(), parsedDhParams
.get());
488 Security::ServerOptions::updateContextSessionId(Security::ContextPointer
&ctx
)
491 if (!staticContextSessionId
.isEmpty())
492 SSL_CTX_set_session_id_context(ctx
.get(), reinterpret_cast<const unsigned char*>(staticContextSessionId
.rawContent()), staticContextSessionId
.length());