]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/ServerOptions.cc
Source Format Enforcement (#763)
[thirdparty/squid.git] / src / security / ServerOptions.cc
1 /*
2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9 #include "squid.h"
10 #include "anyp/PortCfg.h"
11 #include "base/Packable.h"
12 #include "cache_cf.h"
13 #include "fatal.h"
14 #include "globals.h"
15 #include "security/ServerOptions.h"
16 #include "security/Session.h"
17 #include "SquidConfig.h"
18 #if USE_OPENSSL
19 #include "compat/openssl.h"
20 #include "ssl/support.h"
21
22 #if HAVE_OPENSSL_ERR_H
23 #include <openssl/err.h>
24 #endif
25 #endif
26
27 #include <limits>
28
29 Security::ServerOptions &
30 Security::ServerOptions::operator =(const Security::ServerOptions &old) {
31 if (this != &old) {
32 Security::PeerOptions::operator =(old);
33 clientCaFile = old.clientCaFile;
34 dh = old.dh;
35 dhParamsFile = old.dhParamsFile;
36 eecdhCurve = old.eecdhCurve;
37 parsedDhParams = old.parsedDhParams;
38 #if USE_OPENSSL
39 if (auto *stk = SSL_dup_CA_list(old.clientCaStack.get()))
40 clientCaStack = Security::ServerOptions::X509_NAME_STACK_Pointer(stk);
41 else
42 #endif
43 clientCaStack = nullptr;
44
45 staticContextSessionId = old.staticContextSessionId;
46 generateHostCertificates = old.generateHostCertificates;
47 signingCa = old.signingCa;
48 untrustedSigningCa = old.untrustedSigningCa;
49 dynamicCertMemCacheSize = old.dynamicCertMemCacheSize;
50 }
51 return *this;
52 }
53
54 void
55 Security::ServerOptions::parse(const char *token)
56 {
57 if (!*token) {
58 // config says just "ssl" or "tls" (or "tls-")
59 encryptTransport = true;
60 return;
61 }
62
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
68 dh.clear();
69 dhParamsFile.clear();
70 eecdhCurve.clear();
71
72 dh.append(token + 3);
73
74 if (!dh.isEmpty()) {
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
80 dhParamsFile = dh;
81 // empty eecdhCurve means "do not use EECDH"
82 }
83 }
84
85 loadDhParams();
86
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=");
90 return;
91 }
92
93 // backward compatibility for dhparams= configuration
94 dh.clear();
95 dh.append(token + 9);
96 dhParamsFile = dh;
97
98 loadDhParams();
99
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
107 }
108
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;
115
116 } else if (strncmp(token, "context=", 8) == 0) {
117 #if USE_OPENSSL
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.");
122 self_destruct();
123 }
124 #else
125 debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: Option 'context=' requires --with-openssl. Ignoring.");
126 #endif
127
128 } else {
129 // parse generic TLS options
130 Security::PeerOptions::parse(token);
131 }
132 }
133
134 void
135 Security::ServerOptions::dumpCfg(Packable *p, const char *pfx) const
136 {
137 // dump out the generic TLS options
138 Security::PeerOptions::dumpCfg(p, pfx);
139
140 if (!encryptTransport)
141 return; // no other settings are relevant
142
143 // dump the server-only options
144 if (!dh.isEmpty())
145 p->appendf(" %sdh=" SQUIDSBUFPH, pfx, SQUIDSBUFPRINT(dh));
146
147 if (!generateHostCertificates)
148 p->appendf(" %sgenerate-host-certificates=off", pfx);
149
150 if (dynamicCertMemCacheSize != 4*1024*1024) // 4MB default, no 'tls-' prefix
151 p->appendf(" dynamic_cert_mem_cache_size=%" PRIuSIZE "bytes", dynamicCertMemCacheSize);
152
153 if (!staticContextSessionId.isEmpty())
154 p->appendf(" %scontext=" SQUIDSBUFPH, pfx, SQUIDSBUFPRINT(staticContextSessionId));
155 }
156
157 Security::ContextPointer
158 Security::ServerOptions::createBlankContext() const
159 {
160 Security::ContextPointer ctx;
161 #if USE_OPENSSL
162 Ssl::Initialize();
163
164 SSL_CTX *t = SSL_CTX_new(TLS_server_method());
165 if (!t) {
166 const auto x = ERR_get_error();
167 debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x));
168 }
169 ctx = convertContextFromRawPtr(t);
170
171 #elif USE_GNUTLS
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));
176 }
177 ctx = convertContextFromRawPtr(t);
178
179 #else
180 debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: No TLS library");
181
182 #endif
183
184 return ctx;
185 }
186
187 void
188 Security::ServerOptions::initServerContexts(AnyP::PortCfg &port)
189 {
190 const char *portType = AnyP::ProtocolType_str[port.transport.protocol];
191 for (auto &keyData : certs) {
192 keyData.loadFromFiles(port, portType);
193 }
194
195 if (generateHostCertificates) {
196 createSigningContexts(port);
197 }
198
199 if (!certs.empty() && !createStaticServerContext(port)) {
200 char buf[128];
201 fatalf("%s_port %s initialization error", portType, port.s.toUrl(buf, sizeof(buf)));
202 }
203
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.
206 }
207
208 bool
209 Security::ServerOptions::createStaticServerContext(AnyP::PortCfg &port)
210 {
211 updateTlsVersionLimits();
212
213 Security::ContextPointer t(createBlankContext());
214 if (t) {
215
216 #if USE_OPENSSL
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.");
221 }
222
223 const auto &keys = certs.front();
224
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));
228 return false;
229 }
230
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));
234 return false;
235 }
236
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());
241 } else {
242 const auto error = ERR_get_error();
243 debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(error));
244 }
245 }
246
247 #elif USE_GNUTLS
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);
257 }
258 debugs(83, DBG_CRITICAL, "ERROR: Failed to initialize server context with keys from " << whichFile << ": " << Security::ErrorString(x));
259 return false;
260 }
261 // XXX: add cert chain to the context
262 }
263 #endif
264
265 if (!loadClientCaFile())
266 return false;
267
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");
271 return false;
272 }
273 }
274
275 staticContext = std::move(t);
276 return bool(staticContext);
277 }
278
279 void
280 Security::ServerOptions::createSigningContexts(const AnyP::PortCfg &port)
281 {
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.
285
286 signingCa = certs.front();
287
288 const char *portType = AnyP::ProtocolType_str[port.transport.protocol];
289 if (!signingCa.cert) {
290 char buf[128];
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)));
293 }
294
295 if (!signingCa.pkey)
296 debugs(3, DBG_IMPORTANT, "No TLS private key configured for " << portType << "_port " << port.s);
297
298 #if USE_OPENSSL
299 Ssl::generateUntrustedCert(untrustedSigningCa.cert, untrustedSigningCa.pkey, signingCa.cert, signingCa.pkey);
300 #elif USE_GNUTLS
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.");
305 return;
306 #else
307 debugs(83, DBG_CRITICAL, "ERROR: Dynamic TLS certificate generation requires --with-openssl.");
308 return;
309 #endif
310
311 if (!untrustedSigningCa.cert) {
312 char buf[128];
313 fatalf("Unable to generate signing certificate for untrusted sites for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf)));
314 }
315 }
316
317 void
318 Security::ServerOptions::syncCaFiles()
319 {
320 // if caFiles is set, just use that
321 if (caFiles.size())
322 return;
323
324 // otherwise fall back to clientca if it is defined
325 if (!clientCaFile.isEmpty())
326 caFiles.emplace_back(clientCaFile);
327 }
328
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
332 bool
333 Security::ServerOptions::loadClientCaFile()
334 {
335 if (clientCaFile.isEmpty())
336 return true;
337
338 #if USE_OPENSSL
339 auto *stk = SSL_load_client_CA_file(clientCaFile.c_str());
340 clientCaStack = Security::ServerOptions::X509_NAME_STACK_Pointer(stk);
341 #endif
342 if (!clientCaStack) {
343 debugs(83, DBG_CRITICAL, "FATAL: Unable to read client CAs from file: " << clientCaFile);
344 }
345
346 return bool(clientCaStack);
347 }
348
349 void
350 Security::ServerOptions::loadDhParams()
351 {
352 if (dhParamsFile.isEmpty())
353 return;
354
355 #if USE_OPENSSL
356 DH *dhp = nullptr;
357 if (FILE *in = fopen(dhParamsFile.c_str(), "r")) {
358 dhp = PEM_read_DHparams(in, NULL, NULL, NULL);
359 fclose(in);
360 }
361
362 if (!dhp) {
363 debugs(83, DBG_IMPORTANT, "WARNING: Failed to read DH parameters '" << dhParamsFile << "'");
364 return;
365 }
366
367 int codes;
368 if (DH_check(dhp, &codes) == 0) {
369 if (codes) {
370 debugs(83, DBG_IMPORTANT, "WARNING: Failed to verify DH parameters '" << dhParamsFile << "' (" << std::hex << codes << ")");
371 DH_free(dhp);
372 dhp = nullptr;
373 }
374 }
375
376 parsedDhParams.resetWithoutLocking(dhp);
377 #endif
378 }
379
380 bool
381 Security::ServerOptions::updateContextConfig(Security::ContextPointer &ctx)
382 {
383 updateContextOptions(ctx);
384 updateContextSessionId(ctx);
385
386 #if USE_OPENSSL
387 if (parsedFlags & SSL_FLAG_NO_SESSION_REUSE) {
388 SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_OFF);
389 }
390
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);
394 }
395
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));
401 return false;
402 }
403 }
404
405 Ssl::MaybeSetupRsaCallback(ctx);
406 #endif
407
408 updateContextEecdh(ctx);
409 updateContextCa(ctx);
410 updateContextClientCa(ctx);
411
412 #if USE_OPENSSL
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);
415
416 Security::SetSessionCacheCallbacks(ctx);
417 #endif
418 return true;
419 }
420
421 void
422 Security::ServerOptions::updateContextClientCa(Security::ContextPointer &ctx)
423 {
424 #if USE_OPENSSL
425 if (clientCaStack) {
426 ERR_clear_error();
427 if (STACK_OF(X509_NAME) *clientca = SSL_dup_CA_list(clientCaStack.get())) {
428 SSL_CTX_set_client_CA_list(ctx.get(), clientca);
429 } else {
430 auto ssl_error = ERR_get_error();
431 debugs(83, DBG_CRITICAL, "ERROR: Failed to dupe the client CA list: " << Security::ErrorString(ssl_error));
432 return;
433 }
434
435 Ssl::ConfigurePeerVerification(ctx, parsedFlags);
436
437 updateContextCrl(ctx);
438 updateContextTrust(ctx);
439
440 } else {
441 Ssl::DisablePeerVerification(ctx);
442 }
443 #endif
444 }
445
446 void
447 Security::ServerOptions::updateContextEecdh(Security::ContextPointer &ctx)
448 {
449 // set Elliptic Curve details into the server context
450 if (!eecdhCurve.isEmpty()) {
451 debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve << ".");
452
453 #if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
454 int nid = OBJ_sn2nid(eecdhCurve.c_str());
455 if (!nid) {
456 debugs(83, DBG_CRITICAL, "ERROR: Unknown EECDH curve '" << eecdhCurve << "'");
457 return;
458 }
459
460 auto ecdh = EC_KEY_new_by_curve_name(nid);
461 if (!ecdh) {
462 const auto x = ERR_get_error();
463 debugs(83, DBG_CRITICAL, "ERROR: Unable to configure Ephemeral ECDH: " << Security::ErrorString(x));
464 return;
465 }
466
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));
470 }
471 EC_KEY_free(ecdh);
472
473 #else
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.");
476 #endif
477 }
478
479 // set DH parameters into the server context
480 #if USE_OPENSSL
481 if (parsedDhParams) {
482 SSL_CTX_set_tmp_dh(ctx.get(), parsedDhParams.get());
483 }
484 #endif
485 }
486
487 void
488 Security::ServerOptions::updateContextSessionId(Security::ContextPointer &ctx)
489 {
490 #if USE_OPENSSL
491 if (!staticContextSessionId.isEmpty())
492 SSL_CTX_set_session_id_context(ctx.get(), reinterpret_cast<const unsigned char*>(staticContextSessionId.rawContent()), staticContextSessionId.length());
493 #endif
494 }
495