ServerOptions.cc
Go to the documentation of this file.
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 
31  if (this != &old) {
34  dh = old.dh;
36  eecdhCurve = old.eecdhCurve;
38 #if USE_OPENSSL
39  if (auto *stk = SSL_dup_CA_list(old.clientCaStack.get()))
41  else
42 #endif
43  clientCaStack = nullptr;
44 
47  signingCa = old.signingCa;
50  }
51  return *this;
52 }
53 
54 void
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
131  }
132 }
133 
134 void
135 Security::ServerOptions::dumpCfg(Packable *p, const char *pfx) const
136 {
137  // dump out the generic TLS options
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 
159 {
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
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
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
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
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 
332 bool
334 {
335  if (clientCaFile.isEmpty())
336  return true;
337 
338 #if USE_OPENSSL
339  auto *stk = SSL_load_client_CA_file(clientCaFile.c_str());
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
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
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 
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 
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 
417 #endif
418  return true;
419 }
420 
421 void
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 {
442  }
443 #else
444  (void)ctx;
445 #endif
446 }
447 
448 void
450 {
451  // set Elliptic Curve details into the server context
452  if (!eecdhCurve.isEmpty()) {
453  debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve << ".");
454 
455 #if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH)
456  int nid = OBJ_sn2nid(eecdhCurve.c_str());
457  if (!nid) {
458  debugs(83, DBG_CRITICAL, "ERROR: Unknown EECDH curve '" << eecdhCurve << "'");
459  return;
460  }
461 
462  auto ecdh = EC_KEY_new_by_curve_name(nid);
463  if (!ecdh) {
464  const auto x = ERR_get_error();
465  debugs(83, DBG_CRITICAL, "ERROR: Unable to configure Ephemeral ECDH: " << Security::ErrorString(x));
466  return;
467  }
468 
469  if (!SSL_CTX_set_tmp_ecdh(ctx.get(), ecdh)) {
470  const auto x = ERR_get_error();
471  debugs(83, DBG_CRITICAL, "ERROR: Unable to set Ephemeral ECDH: " << Security::ErrorString(x));
472  }
473  EC_KEY_free(ecdh);
474 
475 #else
476  debugs(83, DBG_CRITICAL, "ERROR: EECDH is not available in this build." <<
477  " Please link against OpenSSL>=0.9.8 and ensure OPENSSL_NO_ECDH is not set.");
478  (void)ctx;
479 #endif
480  }
481 
482  // set DH parameters into the server context
483 #if USE_OPENSSL
484  if (parsedDhParams) {
485  SSL_CTX_set_tmp_dh(ctx.get(), parsedDhParams.get());
486  }
487 #endif
488 }
489 
490 void
492 {
493 #if USE_OPENSSL
494  if (!staticContextSessionId.isEmpty())
495  SSL_CTX_set_session_id_context(ctx.get(), reinterpret_cast<const unsigned char*>(staticContextSessionId.rawContent()), staticContextSessionId.length());
496 #else
497  (void)ctx;
498 #endif
499 }
500 
bool updateContextConfig(Security::ContextPointer &)
update the given TLS security context using squid.conf settings
#define SSL_FLAG_DONT_VERIFY_DOMAIN
Definition: forward.h:50
void Initialize()
Definition: support.cc:650
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
std::unique_ptr< STACK_OF(X509_NAME), Security::ServerOptions::sk_X509_NAME_free_wrapper > X509_NAME_STACK_Pointer
Definition: ServerOptions.h:30
bool generateUntrustedCert(Security::CertPointer &untrustedCert, Security::PrivateKeyPointer &untrustedPkey, Security::CertPointer const &cert, Security::PrivateKeyPointer const &pkey)
Definition: support.cc:1365
std::shared_ptr< SSL_CTX > ContextPointer
Definition: Context.h:29
void MaybeSetupRsaCallback(Security::ContextPointer &)
if required, setup callback for generating ephemeral RSA keys
Definition: support.cc:171
struct SquidConfig::@120 SSL
bool createStaticServerContext(AnyP::PortCfg &)
virtual Security::ContextPointer createBlankContext() const
generate an unset security context object
void createSigningContexts(const AnyP::PortCfg &)
virtual void dumpCfg(Packable *, const char *pfx) const
output squid.conf syntax with 'pfx' prefix on parameters for the stored settings
Definition: PeerOptions.cc:105
void error(char *format,...)
static char * keys[]
Definition: WinSvc.cc:84
const char * ProtocolType_str[]
Security::DhePointer parsedDhParams
DH parameters for temporary/ephemeral DH key exchanges.
Definition: SBuf.h:87
void parseBytesOptionValue(size_t *bptr, const char *units, char const *value)
Parse bytes number from a string.
Definition: cache_cf.cc:1396
PeerOptions & operator=(const PeerOptions &)=default
#define DBG_CRITICAL
Definition: Debug.h:40
#define DBG_IMPORTANT
Definition: Debug.h:41
static int port
Definition: ldap_backend.cc:69
void self_destruct(void)
Definition: cache_cf.cc:275
A const & max(A const &lhs, A const &rhs)
bool generateHostCertificates
dynamically make host cert
Definition: ServerOptions.h:75
size_t dynamicCertMemCacheSize
max size of generated certificates memory cache (4 MB default)
Definition: ServerOptions.h:91
SBuf dhParamsFile
Diffi-Helman ciphers parameter file.
#define NULL
Definition: types.h:166
#define SQUIDSBUFPRINT(s)
Definition: SBuf.h:32
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Debug.h:123
void updateContextSessionId(Security::ContextPointer &)
update the context with a configured session ID (if any)
void updateContextClientCa(Security::ContextPointer &)
update the context with CA details used to verify client certificates
#define SSL_FLAG_NO_SESSION_REUSE
Definition: forward.h:51
SBuf staticContextSessionId
"session id context" for staticContext
Definition: ServerOptions.h:72
void SetSessionCacheCallbacks(Security::ContextPointer &)
Setup the given TLS context with callbacks used to manage the session cache.
Definition: Session.cc:375
SBuf clientCaFile
name of file to load client CAs from
void updateContextEecdh(Security::ContextPointer &)
update the context with DH, EDH, EECDH settings
void fatalf(const char *fmt,...)
Definition: fatal.cc:68
int unclean_shutdown
Definition: SquidConfig.h:504
#define DBG_PARSE_NOTE(x)
Definition: Debug.h:45
SBuf dh
Diffi-Helman cipher config.
#define TLS_server_method
Definition: openssl.h:200
SBuf & append(const SBuf &S)
Definition: SBuf.cc:185
void syncCaFiles()
sync the various sources of CA files to be loaded
static const size_type npos
Definition: SBuf.h:92
TLS squid.conf settings for a listening port.
Definition: ServerOptions.h:26
void initServerContexts(AnyP::PortCfg &)
void DisablePeerVerification(Security::ContextPointer &)
Definition: support.cc:435
virtual void dumpCfg(Packable *, const char *pfx) const
output squid.conf syntax with 'pfx' prefix on parameters for the stored settings
Security::KeyData untrustedSigningCa
x509 certificate and key for signing untrusted generated certificates
Definition: ServerOptions.h:88
SBuf eecdhCurve
Elliptic curve for ephemeral EC-based DH key exchanges.
virtual void parse(const char *)
parse a TLS squid.conf option
SBuf & appendf(const char *fmt,...)
Definition: SBuf.cc:229
int ssl_ctx_ex_index_dont_verify_domain
void ConfigurePeerVerification(Security::ContextPointer &, const Security::ParsedPortFlags)
set the certificate verify callback for a context
Definition: support.cc:408
ServerOptions & operator=(const ServerOptions &)
STACK_OF(X509) *X509_STORE_CTX_get0_untrusted(X509_STORE_CTX *ctx)
Definition: openssl.h:237
const char * ErrorString(const LibErrorCode code)
converts numeric LibErrorCode into a human-friendlier string
Definition: forward.h:123
Security::KeyData signingCa
x509 certificate and key for signing generated certificates
Definition: ServerOptions.h:87
#define SQUIDSBUFPH
Definition: SBuf.h:31
class SquidConfig Config
Definition: SquidConfig.cc:12
virtual void parse(const char *)
parse a TLS squid.conf option
Definition: PeerOptions.cc:33
X509_NAME_STACK_Pointer clientCaStack
CA certificate(s) to use when verifying client certificates.

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors