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