]>
Commit | Line | Data |
---|---|---|
474f076e | 1 | /* |
5b74111a | 2 | * Copyright (C) 1996-2018 The Squid Software Foundation and contributors |
474f076e AJ |
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" | |
cf487124 | 10 | #include "anyp/PortCfg.h" |
474f076e | 11 | #include "base/Packable.h" |
cf487124 AJ |
12 | #include "cache_cf.h" |
13 | #include "fatal.h" | |
474f076e AJ |
14 | #include "globals.h" |
15 | #include "security/ServerOptions.h" | |
cf487124 AJ |
16 | #include "security/Session.h" |
17 | #include "SquidConfig.h" | |
0a28c16a AJ |
18 | #if USE_OPENSSL |
19 | #include "ssl/support.h" | |
20 | #endif | |
474f076e AJ |
21 | |
22 | #if HAVE_OPENSSL_ERR_H | |
23 | #include <openssl/err.h> | |
24 | #endif | |
25 | #if HAVE_OPENSSL_X509_H | |
26 | #include <openssl/x509.h> | |
27 | #endif | |
28 | ||
621f4299 AJ |
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); | |
cf487124 | 41 | else |
621f4299 | 42 | #endif |
cf487124 AJ |
43 | clientCaStack = nullptr; |
44 | ||
45 | staticContextSessionId = old.staticContextSessionId; | |
46 | generateHostCertificates = old.generateHostCertificates; | |
47 | signingCert = old.signingCert; | |
48 | signPkey = old.signPkey; | |
49 | certsToChain = old.certsToChain; | |
50 | untrustedSigningCert = old.untrustedSigningCert; | |
51 | untrustedSignPkey = old.untrustedSignPkey; | |
52 | dynamicCertMemCacheSize = old.dynamicCertMemCacheSize; | |
621f4299 AJ |
53 | } |
54 | return *this; | |
55 | } | |
56 | ||
474f076e AJ |
57 | void |
58 | Security::ServerOptions::parse(const char *token) | |
59 | { | |
60 | if (!*token) { | |
61 | // config says just "ssl" or "tls" (or "tls-") | |
62 | encryptTransport = true; | |
63 | return; | |
64 | } | |
65 | ||
66 | // parse the server-only options | |
67 | if (strncmp(token, "dh=", 3) == 0) { | |
68 | // clear any previous Diffi-Helman configuration | |
69 | dh.clear(); | |
70 | dhParamsFile.clear(); | |
71 | eecdhCurve.clear(); | |
72 | ||
73 | dh.append(token + 3); | |
74 | ||
75 | if (!dh.isEmpty()) { | |
76 | auto pos = dh.find(':'); | |
77 | if (pos != SBuf::npos) { // tls-dh=eecdhCurve:dhParamsFile | |
78 | eecdhCurve = dh.substr(0,pos); | |
79 | dhParamsFile = dh.substr(pos+1); | |
80 | } else { // tls-dh=dhParamsFile | |
81 | dhParamsFile = dh; | |
82 | // empty eecdhCurve means "do not use EECDH" | |
83 | } | |
84 | } | |
85 | ||
104deb98 AJ |
86 | loadDhParams(); |
87 | ||
474f076e AJ |
88 | } else if (strncmp(token, "dhparams=", 9) == 0) { |
89 | if (!eecdhCurve.isEmpty()) { | |
90 | debugs(83, DBG_PARSE_NOTE(1), "UPGRADE WARNING: EECDH settings in tls-dh= override dhparams="); | |
91 | return; | |
92 | } | |
93 | ||
94 | // backward compatibility for dhparams= configuration | |
95 | dh.clear(); | |
96 | dh.append(token + 9); | |
97 | dhParamsFile = dh; | |
98 | ||
104deb98 AJ |
99 | loadDhParams(); |
100 | ||
cf487124 AJ |
101 | } else if (strncmp(token, "dynamic_cert_mem_cache_size=", 28) == 0) { |
102 | parseBytesOptionValue(&dynamicCertMemCacheSize, "bytes", token + 28); | |
103 | // XXX: parseBytesOptionValue() self_destruct()s on invalid values, | |
104 | // probably making this comparison and misleading ERROR unnecessary. | |
105 | if (dynamicCertMemCacheSize == std::numeric_limits<size_t>::max()) { | |
106 | debugs(3, DBG_CRITICAL, "ERROR: Cannot allocate memory for '" << token << "'. Using default of 4MB instead."); | |
107 | dynamicCertMemCacheSize = 4*1024*1024; // 4 MB | |
108 | } | |
109 | ||
110 | } else if (strcmp(token, "generate-host-certificates") == 0) { | |
111 | generateHostCertificates = true; | |
112 | } else if (strcmp(token, "generate-host-certificates=on") == 0) { | |
113 | generateHostCertificates = true; | |
114 | } else if (strcmp(token, "generate-host-certificates=off") == 0) { | |
115 | generateHostCertificates = false; | |
116 | ||
117 | } else if (strncmp(token, "context=", 8) == 0) { | |
118 | #if USE_OPENSSL | |
119 | staticContextSessionId = SBuf(token+8); | |
120 | // to hide its arguably sensitive value, do not print token in these debugs | |
121 | if (staticContextSessionId.length() > SSL_MAX_SSL_SESSION_ID_LENGTH) { | |
122 | debugs(83, DBG_CRITICAL, "FATAL: Option 'context=' value is too long. Maximum " << SSL_MAX_SSL_SESSION_ID_LENGTH << " characters."); | |
123 | self_destruct(); | |
124 | } | |
125 | #else | |
126 | debugs(83, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: Option 'context=' requires --with-openssl. Ignoring."); | |
127 | #endif | |
128 | ||
474f076e AJ |
129 | } else { |
130 | // parse generic TLS options | |
131 | Security::PeerOptions::parse(token); | |
132 | } | |
133 | } | |
134 | ||
135 | void | |
136 | Security::ServerOptions::dumpCfg(Packable *p, const char *pfx) const | |
137 | { | |
138 | // dump out the generic TLS options | |
139 | Security::PeerOptions::dumpCfg(p, pfx); | |
140 | ||
141 | if (!encryptTransport) | |
142 | return; // no other settings are relevant | |
143 | ||
144 | // dump the server-only options | |
145 | if (!dh.isEmpty()) | |
146 | p->appendf(" %sdh=" SQUIDSBUFPH, pfx, SQUIDSBUFPRINT(dh)); | |
cf487124 AJ |
147 | |
148 | if (!generateHostCertificates) | |
149 | p->appendf(" %sgenerate-host-certificates=off", pfx); | |
150 | ||
151 | if (dynamicCertMemCacheSize != 4*1024*1024) // 4MB default, no 'tls-' prefix | |
152 | p->appendf(" dynamic_cert_mem_cache_size=%" PRIuSIZE "bytes", dynamicCertMemCacheSize); | |
153 | ||
154 | if (!staticContextSessionId.isEmpty()) | |
155 | p->appendf(" %scontext=" SQUIDSBUFPH, pfx, SQUIDSBUFPRINT(staticContextSessionId)); | |
474f076e AJ |
156 | } |
157 | ||
64769c79 | 158 | Security::ContextPointer |
885f0ecf AJ |
159 | Security::ServerOptions::createBlankContext() const |
160 | { | |
64769c79 | 161 | Security::ContextPointer ctx; |
885f0ecf | 162 | #if USE_OPENSSL |
0a28c16a AJ |
163 | Ssl::Initialize(); |
164 | ||
8d56fe55 | 165 | #if HAVE_OPENSSL_SERVER_METHOD |
64769c79 | 166 | SSL_CTX *t = SSL_CTX_new(TLS_server_method()); |
885f0ecf | 167 | #else |
64769c79 | 168 | SSL_CTX *t = SSL_CTX_new(SSLv23_server_method()); |
885f0ecf AJ |
169 | #endif |
170 | if (!t) { | |
ea574635 AJ |
171 | const auto x = ERR_get_error(); |
172 | debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x)); | |
885f0ecf | 173 | } |
df473b36 | 174 | ctx = convertContextFromRawPtr(t); |
885f0ecf AJ |
175 | |
176 | #elif USE_GNUTLS | |
177 | // Initialize for X.509 certificate exchange | |
64769c79 | 178 | gnutls_certificate_credentials_t t; |
885f0ecf | 179 | if (const int x = gnutls_certificate_allocate_credentials(&t)) { |
ea574635 | 180 | debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: " << Security::ErrorString(x)); |
885f0ecf | 181 | } |
df473b36 | 182 | ctx = convertContextFromRawPtr(t); |
885f0ecf AJ |
183 | |
184 | #else | |
185 | debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate TLS server context: No TLS library"); | |
186 | ||
187 | #endif | |
188 | ||
64769c79 | 189 | return ctx; |
885f0ecf AJ |
190 | } |
191 | ||
9ad528b8 | 192 | bool |
c75aba02 AJ |
193 | Security::ServerOptions::createStaticServerContext(AnyP::PortCfg &port) |
194 | { | |
195 | updateTlsVersionLimits(); | |
196 | ||
9ad528b8 | 197 | Security::ContextPointer t(createBlankContext()); |
c75aba02 AJ |
198 | if (t) { |
199 | #if USE_OPENSSL | |
9ad528b8 AJ |
200 | if (!Ssl::InitServerContext(t, port)) |
201 | return false; | |
c75aba02 | 202 | #endif |
621f4299 AJ |
203 | if (!loadClientCaFile()) |
204 | return false; | |
c75aba02 AJ |
205 | } |
206 | ||
9ad528b8 AJ |
207 | staticContext = std::move(t); |
208 | return bool(staticContext); | |
c75aba02 AJ |
209 | } |
210 | ||
cf487124 AJ |
211 | void |
212 | Security::ServerOptions::createSigningContexts(AnyP::PortCfg &port) | |
213 | { | |
214 | const char *portType = AnyP::ProtocolType_str[port.transport.protocol]; | |
215 | if (!certs.empty()) { | |
216 | #if USE_OPENSSL | |
217 | Security::KeyData &keys = certs.front(); | |
218 | Ssl::readCertChainAndPrivateKeyFromFiles(signingCert, signPkey, certsToChain, keys.certFile.c_str(), keys.privateKeyFile.c_str()); | |
219 | #else | |
220 | char buf[128]; | |
221 | fatalf("Directive '%s_port %s' requires --with-openssl.", portType, port.s.toUrl(buf, sizeof(buf))); | |
222 | #endif | |
223 | } | |
224 | ||
225 | if (!signingCert) { | |
226 | char buf[128]; | |
227 | fatalf("No valid signing SSL certificate configured for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf))); | |
228 | } | |
229 | ||
230 | if (!signPkey) | |
231 | debugs(3, DBG_IMPORTANT, "No SSL private key configured for " << portType << "_port " << port.s); | |
232 | ||
233 | #if USE_OPENSSL | |
234 | Ssl::generateUntrustedCert(untrustedSigningCert, untrustedSignPkey, signingCert, signPkey); | |
235 | #endif | |
236 | ||
237 | if (!untrustedSigningCert) { | |
238 | char buf[128]; | |
239 | fatalf("Unable to generate signing SSL certificate for untrusted sites for %s_port %s", portType, port.s.toUrl(buf, sizeof(buf))); | |
240 | } | |
241 | ||
242 | if (!createStaticServerContext(port)) { | |
243 | char buf[128]; | |
244 | fatalf("%s_port %s initialization error", portType, port.s.toUrl(buf, sizeof(buf))); | |
245 | } | |
246 | } | |
247 | ||
621f4299 AJ |
248 | void |
249 | Security::ServerOptions::syncCaFiles() | |
250 | { | |
251 | // if caFiles is set, just use that | |
252 | if (caFiles.size()) | |
253 | return; | |
254 | ||
255 | // otherwise fall back to clientca if it is defined | |
256 | if (!clientCaFile.isEmpty()) | |
257 | caFiles.emplace_back(clientCaFile); | |
258 | } | |
259 | ||
260 | /// load clientca= file (if any) into memory. | |
261 | /// \retval true clientca is not set, or loaded successfully | |
262 | /// \retval false unable to load the file, or not using OpenSSL | |
263 | bool | |
264 | Security::ServerOptions::loadClientCaFile() | |
265 | { | |
266 | if (clientCaFile.isEmpty()) | |
267 | return true; | |
268 | ||
269 | #if USE_OPENSSL | |
270 | auto *stk = SSL_load_client_CA_file(clientCaFile.c_str()); | |
271 | clientCaStack = Security::ServerOptions::X509_NAME_STACK_Pointer(stk); | |
272 | #endif | |
273 | if (!clientCaStack) { | |
274 | debugs(83, DBG_CRITICAL, "FATAL: Unable to read client CAs from file: " << clientCaFile); | |
275 | } | |
276 | ||
277 | return bool(clientCaStack); | |
278 | } | |
279 | ||
474f076e | 280 | void |
104deb98 | 281 | Security::ServerOptions::loadDhParams() |
474f076e | 282 | { |
104deb98 | 283 | if (dhParamsFile.isEmpty()) |
474f076e AJ |
284 | return; |
285 | ||
104deb98 AJ |
286 | #if USE_OPENSSL |
287 | DH *dhp = nullptr; | |
288 | if (FILE *in = fopen(dhParamsFile.c_str(), "r")) { | |
289 | dhp = PEM_read_DHparams(in, NULL, NULL, NULL); | |
290 | fclose(in); | |
474f076e AJ |
291 | } |
292 | ||
104deb98 AJ |
293 | if (!dhp) { |
294 | debugs(83, DBG_IMPORTANT, "WARNING: Failed to read DH parameters '" << dhParamsFile << "'"); | |
474f076e AJ |
295 | return; |
296 | } | |
297 | ||
104deb98 AJ |
298 | int codes; |
299 | if (DH_check(dhp, &codes) == 0) { | |
300 | if (codes) { | |
301 | debugs(83, DBG_IMPORTANT, "WARNING: Failed to verify DH parameters '" << dhParamsFile << "' (" << std::hex << codes << ")"); | |
302 | DH_free(dhp); | |
303 | dhp = nullptr; | |
304 | } | |
474f076e | 305 | } |
104deb98 | 306 | |
35b3559c | 307 | parsedDhParams.resetWithoutLocking(dhp); |
104deb98 AJ |
308 | #endif |
309 | } | |
310 | ||
cf487124 AJ |
311 | bool |
312 | Security::ServerOptions::updateContextConfig(Security::ContextPointer &ctx) | |
313 | { | |
314 | updateContextOptions(ctx); | |
315 | updateContextSessionId(ctx); | |
316 | ||
317 | #if USE_OPENSSL | |
318 | if (parsedFlags & SSL_FLAG_NO_SESSION_REUSE) { | |
319 | SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_OFF); | |
320 | } | |
321 | ||
322 | if (Config.SSL.unclean_shutdown) { | |
323 | debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation)."); | |
324 | SSL_CTX_set_quiet_shutdown(ctx.get(), 1); | |
325 | } | |
326 | ||
327 | if (!sslCipher.isEmpty()) { | |
328 | debugs(83, 5, "Using cipher suite " << sslCipher << "."); | |
329 | if (!SSL_CTX_set_cipher_list(ctx.get(), sslCipher.c_str())) { | |
330 | auto ssl_error = ERR_get_error(); | |
331 | debugs(83, DBG_CRITICAL, "ERROR: Failed to set SSL cipher suite '" << sslCipher << "': " << Security::ErrorString(ssl_error)); | |
332 | return false; | |
333 | } | |
334 | } | |
335 | ||
336 | Ssl::MaybeSetupRsaCallback(ctx); | |
337 | #endif | |
338 | ||
339 | updateContextEecdh(ctx); | |
340 | updateContextCa(ctx); | |
341 | updateContextClientCa(ctx); | |
342 | ||
343 | #if USE_OPENSSL | |
344 | if (parsedFlags & SSL_FLAG_DONT_VERIFY_DOMAIN) | |
345 | SSL_CTX_set_ex_data(ctx.get(), ssl_ctx_ex_index_dont_verify_domain, (void *) -1); | |
346 | ||
347 | Security::SetSessionCacheCallbacks(ctx); | |
348 | #endif | |
349 | return true; | |
350 | } | |
351 | ||
621f4299 AJ |
352 | void |
353 | Security::ServerOptions::updateContextClientCa(Security::ContextPointer &ctx) | |
354 | { | |
355 | #if USE_OPENSSL | |
356 | if (clientCaStack) { | |
357 | ERR_clear_error(); | |
358 | if (STACK_OF(X509_NAME) *clientca = SSL_dup_CA_list(clientCaStack.get())) { | |
359 | SSL_CTX_set_client_CA_list(ctx.get(), clientca); | |
360 | } else { | |
361 | auto ssl_error = ERR_get_error(); | |
362 | debugs(83, DBG_CRITICAL, "ERROR: Failed to dupe the client CA list: " << Security::ErrorString(ssl_error)); | |
363 | return; | |
364 | } | |
365 | ||
366 | if (parsedFlags & SSL_FLAG_DELAYED_AUTH) { | |
367 | debugs(83, 9, "Not requesting client certificates until acl processing requires one"); | |
368 | SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, nullptr); | |
369 | } else { | |
370 | debugs(83, 9, "Requiring client certificates."); | |
371 | Ssl::SetupVerifyCallback(ctx); | |
372 | } | |
373 | ||
374 | updateContextCrl(ctx); | |
375 | ||
376 | } else { | |
377 | debugs(83, 9, "Not requiring any client certificates"); | |
378 | SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, NULL); | |
379 | } | |
380 | #endif | |
381 | } | |
382 | ||
104deb98 | 383 | void |
b23f5f9c | 384 | Security::ServerOptions::updateContextEecdh(Security::ContextPointer &ctx) |
104deb98 AJ |
385 | { |
386 | // set Elliptic Curve details into the server context | |
387 | if (!eecdhCurve.isEmpty()) { | |
388 | debugs(83, 9, "Setting Ephemeral ECDH curve to " << eecdhCurve << "."); | |
389 | ||
390 | #if USE_OPENSSL && OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_ECDH) | |
391 | int nid = OBJ_sn2nid(eecdhCurve.c_str()); | |
392 | if (!nid) { | |
393 | debugs(83, DBG_CRITICAL, "ERROR: Unknown EECDH curve '" << eecdhCurve << "'"); | |
394 | return; | |
395 | } | |
396 | ||
397 | auto ecdh = EC_KEY_new_by_curve_name(nid); | |
398 | if (!ecdh) { | |
ea574635 AJ |
399 | const auto x = ERR_get_error(); |
400 | debugs(83, DBG_CRITICAL, "ERROR: Unable to configure Ephemeral ECDH: " << Security::ErrorString(x)); | |
104deb98 AJ |
401 | return; |
402 | } | |
403 | ||
b23f5f9c | 404 | if (!SSL_CTX_set_tmp_ecdh(ctx.get(), ecdh)) { |
ea574635 AJ |
405 | const auto x = ERR_get_error(); |
406 | debugs(83, DBG_CRITICAL, "ERROR: Unable to set Ephemeral ECDH: " << Security::ErrorString(x)); | |
104deb98 AJ |
407 | } |
408 | EC_KEY_free(ecdh); | |
409 | ||
474f076e | 410 | #else |
104deb98 AJ |
411 | debugs(83, DBG_CRITICAL, "ERROR: EECDH is not available in this build." << |
412 | " Please link against OpenSSL>=0.9.8 and ensure OPENSSL_NO_ECDH is not set."); | |
413 | #endif | |
414 | } | |
415 | ||
416 | // set DH parameters into the server context | |
417 | #if USE_OPENSSL | |
b1a522a0 | 418 | if (parsedDhParams) { |
b23f5f9c | 419 | SSL_CTX_set_tmp_dh(ctx.get(), parsedDhParams.get()); |
104deb98 | 420 | } |
474f076e AJ |
421 | #endif |
422 | } | |
423 | ||
cf487124 AJ |
424 | void |
425 | Security::ServerOptions::updateContextSessionId(Security::ContextPointer &ctx) | |
426 | { | |
427 | #if USE_OPENSSL | |
428 | if (!staticContextSessionId.isEmpty()) | |
429 | SSL_CTX_set_session_id_context(ctx.get(), reinterpret_cast<const unsigned char*>(staticContextSessionId.rawContent()), staticContextSessionId.length()); | |
430 | #endif | |
431 | } | |
432 |