]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/ServerOptions.cc
Drop PRIuSIZE macro (#1388)
[thirdparty/squid.git] / src / security / ServerOptions.cc
1 /*
2 * Copyright (C) 1996-2023 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 "error/SysErrorDetail.h"
14 #include "fatal.h"
15 #include "globals.h"
16 #include "security/ServerOptions.h"
17 #include "security/Session.h"
18 #include "SquidConfig.h"
19 #if USE_OPENSSL
20 #include "compat/openssl.h"
21 #include "ssl/support.h"
22
23 #if HAVE_OPENSSL_DECODER_H
24 #include <openssl/decoder.h>
25 #endif
26 #if HAVE_OPENSSL_ERR_H
27 #include <openssl/err.h>
28 #endif
29 #endif
30
31 #include <limits>
32
33 Security::ServerOptions &
34 Security::ServerOptions::operator =(const Security::ServerOptions &old) {
35 if (this != &old) {
36 Security::PeerOptions::operator =(old);
37 clientCaFile = old.clientCaFile;
38 dh = old.dh;
39 dhParamsFile = old.dhParamsFile;
40 eecdhCurve = old.eecdhCurve;
41 parsedDhParams = old.parsedDhParams;
42 #if USE_OPENSSL
43 if (auto *stk = SSL_dup_CA_list(old.clientCaStack.get()))
44 clientCaStack = Security::ServerOptions::X509_NAME_STACK_Pointer(stk);
45 else
46 #endif
47 clientCaStack = nullptr;
48
49 staticContextSessionId = old.staticContextSessionId;
50 generateHostCertificates = old.generateHostCertificates;
51 signingCa = old.signingCa;
52 untrustedSigningCa = old.untrustedSigningCa;
53 dynamicCertMemCacheSize = old.dynamicCertMemCacheSize;
54 }
55 return *this;
56 }
57
58 void
59 Security::ServerOptions::parse(const char *token)
60 {
61 if (!*token) {
62 // config says just "ssl" or "tls" (or "tls-")
63 encryptTransport = true;
64 return;
65 }
66
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
72 dh.clear();
73 dhParamsFile.clear();
74 eecdhCurve.clear();
75
76 dh.append(token + 3);
77
78 if (!dh.isEmpty()) {
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
84 dhParamsFile = dh;
85 // empty eecdhCurve means "do not use EECDH"
86 }
87 }
88
89 loadDhParams();
90
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=");
94 return;
95 }
96
97 // backward compatibility for dhparams= configuration
98 dh.clear();
99 dh.append(token + 9);
100 dhParamsFile = dh;
101
102 loadDhParams();
103
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
111 }
112
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;
119
120 } else if (strncmp(token, "context=", 8) == 0) {
121 #if USE_OPENSSL
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.");
126 self_destruct();
127 }
128 #else
129 debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: Option 'context=' requires --with-openssl. Ignoring.");
130 #endif
131
132 } else {
133 // parse generic TLS options
134 Security::PeerOptions::parse(token);
135 }
136 }
137
138 void
139 Security::ServerOptions::dumpCfg(Packable *p, const char *pfx) const
140 {
141 // dump out the generic TLS options
142 Security::PeerOptions::dumpCfg(p, pfx);
143
144 if (!encryptTransport)
145 return; // no other settings are relevant
146
147 // dump the server-only options
148 if (!dh.isEmpty())
149 p->appendf(" %sdh=" SQUIDSBUFPH, pfx, SQUIDSBUFPRINT(dh));
150
151 if (!generateHostCertificates)
152 p->appendf(" %sgenerate-host-certificates=off", pfx);
153
154 if (dynamicCertMemCacheSize != 4*1024*1024) // 4MB default, no 'tls-' prefix
155 p->appendf(" dynamic_cert_mem_cache_size=%zubytes", dynamicCertMemCacheSize);
156
157 if (!staticContextSessionId.isEmpty())
158 p->appendf(" %scontext=" SQUIDSBUFPH, pfx, SQUIDSBUFPRINT(staticContextSessionId));
159 }
160
161 Security::ContextPointer
162 Security::ServerOptions::createBlankContext() const
163 {
164 Security::ContextPointer ctx;
165 #if USE_OPENSSL
166 Ssl::Initialize();
167
168 SSL_CTX *t = SSL_CTX_new(TLS_server_method());
169 if (!t) {
170 const auto x = ERR_get_error();
171 debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x));
172 }
173 ctx = convertContextFromRawPtr(t);
174
175 #elif USE_GNUTLS
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));
180 }
181 ctx = convertContextFromRawPtr(t);
182
183 #else
184 debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: No TLS library");
185
186 #endif
187
188 return ctx;
189 }
190
191 void
192 Security::ServerOptions::initServerContexts(AnyP::PortCfg &port)
193 {
194 const char *portType = AnyP::ProtocolType_str[port.transport.protocol];
195 for (auto &keyData : certs) {
196 keyData.loadFromFiles(port, portType);
197 }
198
199 if (generateHostCertificates) {
200 createSigningContexts(port);
201 }
202
203 if (!certs.empty() && !createStaticServerContext(port)) {
204 char buf[128];
205 fatalf("%s_port %s initialization error", portType, port.s.toUrl(buf, sizeof(buf)));
206 }
207
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.
210 }
211
212 bool
213 Security::ServerOptions::createStaticServerContext(AnyP::PortCfg &)
214 {
215 updateTlsVersionLimits();
216
217 Security::ContextPointer t(createBlankContext());
218 if (t) {
219
220 #if USE_OPENSSL
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.");
225 }
226
227 const auto &keys = certs.front();
228
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));
232 return false;
233 }
234
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));
238 return false;
239 }
240
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());
245 } else {
246 const auto error = ERR_get_error();
247 debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << Security::ErrorString(error));
248 }
249 }
250
251 #elif USE_GNUTLS
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);
261 }
262 debugs(83, DBG_CRITICAL, "ERROR: Failed to initialize server context with keys from " << whichFile << ": " << Security::ErrorString(x));
263 return false;
264 }
265 // XXX: add cert chain to the context
266 }
267 #endif
268
269 if (!loadClientCaFile())
270 return false;
271
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");
275 return false;
276 }
277 }
278
279 staticContext = std::move(t);
280 return bool(staticContext);
281 }
282
283 void
284 Security::ServerOptions::createSigningContexts(const AnyP::PortCfg &port)
285 {
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.
289
290 signingCa = certs.front();
291
292 const char *portType = AnyP::ProtocolType_str[port.transport.protocol];
293 if (!signingCa.cert) {
294 char buf[128];
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)));
297 }
298
299 if (!signingCa.pkey)
300 debugs(3, DBG_IMPORTANT, "No TLS private key configured for " << portType << "_port " << port.s);
301
302 #if USE_OPENSSL
303 Ssl::generateUntrustedCert(untrustedSigningCa.cert, untrustedSigningCa.pkey, signingCa.cert, signingCa.pkey);
304 #elif USE_GNUTLS
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.");
309 return;
310 #else
311 debugs(83, DBG_CRITICAL, "ERROR: Dynamic TLS certificate generation requires --with-openssl.");
312 return;
313 #endif
314
315 if (!untrustedSigningCa.cert) {
316 char buf[128];
317 fatalf("Unable to generate signing certificate for untrusted sites for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf)));
318 }
319 }
320
321 void
322 Security::ServerOptions::syncCaFiles()
323 {
324 // if caFiles is set, just use that
325 if (caFiles.size())
326 return;
327
328 // otherwise fall back to clientca if it is defined
329 if (!clientCaFile.isEmpty())
330 caFiles.emplace_back(clientCaFile);
331 }
332
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
336 bool
337 Security::ServerOptions::loadClientCaFile()
338 {
339 if (clientCaFile.isEmpty())
340 return true;
341
342 #if USE_OPENSSL
343 auto *stk = SSL_load_client_CA_file(clientCaFile.c_str());
344 clientCaStack = Security::ServerOptions::X509_NAME_STACK_Pointer(stk);
345 #endif
346 if (!clientCaStack) {
347 debugs(83, DBG_CRITICAL, "FATAL: Unable to read client CAs from file: " << clientCaFile);
348 }
349
350 return bool(clientCaStack);
351 }
352
353 void
354 Security::ServerOptions::loadDhParams()
355 {
356 if (dhParamsFile.isEmpty())
357 return;
358
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().
362
363 #if USE_OPENSSL
364 #if OPENSSL_VERSION_MAJOR < 3
365 DH *dhp = nullptr;
366 if (FILE *in = fopen(dhParamsFile.c_str(), "r")) {
367 dhp = PEM_read_DHparams(in, nullptr, nullptr, nullptr);
368 fclose(in);
369 } else {
370 const auto xerrno = errno;
371 debugs(83, DBG_IMPORTANT, "WARNING: Failed to open '" << dhParamsFile << "'" << ReportSysError(xerrno));
372 return;
373 }
374
375 if (!dhp) {
376 debugs(83, DBG_IMPORTANT, "WARNING: Failed to read DH parameters '" << dhParamsFile << "'");
377 return;
378 }
379
380 int codes;
381 if (DH_check(dhp, &codes) == 0) {
382 if (codes) {
383 debugs(83, DBG_IMPORTANT, "WARNING: Failed to verify DH parameters '" << dhParamsFile << "' (" << std::hex << codes << ")");
384 DH_free(dhp);
385 dhp = nullptr;
386 }
387 }
388
389 parsedDhParams.resetWithoutLocking(dhp);
390
391 #else // OpenSSL 3.0+
392 const auto type = eecdhCurve.isEmpty() ? "DH" : "EC";
393
394 Ssl::ForgetErrors();
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)}) {
398
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.
404 assert(!rawPkey);
405
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);
408 return;
409 }
410
411 if (const auto in = fopen(dhParamsFile.c_str(), "r")) {
412 if (OSSL_DECODER_from_fp(dctx.get(), in)) {
413 assert(rawPkey);
414 const Security::DhePointer pkey(rawPkey);
415 // TODO: verify that the loaded parameters match the curve named in eecdhCurve
416
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())) {
419 case 1: // success
420 parsedDhParams = pkey;
421 break;
422 case -2:
423 debugs(83, DBG_PARSE_NOTE(2), "WARNING: OpenSSL does not support " << type << " parameters check: " << dhParamsFile << Ssl::ReportAndForgetErrors);
424 break;
425 default:
426 debugs(83, DBG_IMPORTANT, "ERROR: Failed to verify " << type << " parameters in " << dhParamsFile << Ssl::ReportAndForgetErrors);
427 break;
428 }
429 } else {
430 // TODO: Reduce error reporting code duplication.
431 debugs(83, DBG_IMPORTANT, "ERROR: Cannot check " << type << " parameters in " << dhParamsFile << Ssl::ReportAndForgetErrors);
432 }
433 } else {
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
436 }
437 fclose(in);
438 } else {
439 const auto xerrno = errno;
440 debugs(83, DBG_IMPORTANT, "WARNING: Failed to open '" << dhParamsFile << "'" << ReportSysError(xerrno));
441 }
442
443 } else {
444 debugs(83, DBG_IMPORTANT, "WARNING: Unable to create decode context for " << type << " parameters" << Ssl::ReportAndForgetErrors);
445 return;
446 }
447 #endif
448 #endif // USE_OPENSSL
449 }
450
451 bool
452 Security::ServerOptions::updateContextConfig(Security::ContextPointer &ctx)
453 {
454 updateContextOptions(ctx);
455 updateContextSessionId(ctx);
456
457 #if USE_OPENSSL
458 if (parsedFlags & SSL_FLAG_NO_SESSION_REUSE) {
459 SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_OFF);
460 }
461
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);
465 }
466
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));
472 return false;
473 }
474 }
475
476 Ssl::MaybeSetupRsaCallback(ctx);
477 #endif
478
479 updateContextEecdh(ctx);
480 updateContextCa(ctx);
481 updateContextClientCa(ctx);
482
483 #if USE_OPENSSL
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);
487
488 Security::SetSessionCacheCallbacks(ctx);
489 #endif
490 return true;
491 }
492
493 void
494 Security::ServerOptions::updateContextClientCa(Security::ContextPointer &ctx)
495 {
496 #if USE_OPENSSL
497 if (clientCaStack) {
498 ERR_clear_error();
499 if (STACK_OF(X509_NAME) *clientca = SSL_dup_CA_list(clientCaStack.get())) {
500 SSL_CTX_set_client_CA_list(ctx.get(), clientca);
501 } else {
502 auto ssl_error = ERR_get_error();
503 debugs(83, DBG_CRITICAL, "ERROR: Failed to dupe the client CA list: " << Security::ErrorString(ssl_error));
504 return;
505 }
506
507 Ssl::ConfigurePeerVerification(ctx, parsedFlags);
508
509 updateContextCrl(ctx);
510 updateContextTrust(ctx);
511
512 } else {
513 Ssl::DisablePeerVerification(ctx);
514 }
515 #else
516 (void)ctx;
517 #endif
518 }
519
520 void
521 Security::ServerOptions::updateContextEecdh(Security::ContextPointer &ctx)
522 {
523 // set Elliptic Curve details into the server context
524 if (!eecdhCurve.isEmpty()) {
525 debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve << ".");
526
527 #if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
528
529 Ssl::ForgetErrors();
530
531 int nid = OBJ_sn2nid(eecdhCurve.c_str());
532 if (!nid) {
533 debugs(83, DBG_CRITICAL, "ERROR: Unknown EECDH curve '" << eecdhCurve << "'");
534 return;
535 }
536
537 #if OPENSSL_VERSION_MAJOR < 3
538 auto ecdh = EC_KEY_new_by_curve_name(nid);
539 if (!ecdh) {
540 const auto x = ERR_get_error();
541 debugs(83, DBG_CRITICAL, "ERROR: Unable to configure Ephemeral ECDH: " << Security::ErrorString(x));
542 return;
543 }
544
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));
548 }
549 EC_KEY_free(ecdh);
550
551 #else
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);
555 return;
556 }
557 #endif
558 #else
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.");
561 (void)ctx;
562 #endif
563 }
564
565 // set DH parameters into the server context
566 #if USE_OPENSSL
567 if (parsedDhParams) {
568 SSL_CTX_set_tmp_dh(ctx.get(), parsedDhParams.get());
569 }
570 #endif
571 }
572
573 void
574 Security::ServerOptions::updateContextSessionId(Security::ContextPointer &ctx)
575 {
576 #if USE_OPENSSL
577 if (!staticContextSessionId.isEmpty())
578 SSL_CTX_set_session_id_context(ctx.get(), reinterpret_cast<const unsigned char*>(staticContextSessionId.rawContent()), staticContextSessionId.length());
579 #else
580 (void)ctx;
581 #endif
582 }
583