]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ssl/helper.cc
6ea027ae8fc73f4b811e1b463180363cae987988
[thirdparty/squid.git] / src / ssl / helper.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 "../helper.h"
11 #include "anyp/PortCfg.h"
12 #include "base/AsyncCallbacks.h"
13 #include "cache_cf.h"
14 #include "fs_io.h"
15 #include "helper/Reply.h"
16 #include "Parsing.h"
17 #include "sbuf/Stream.h"
18 #include "SquidConfig.h"
19 #include "SquidString.h"
20 #include "ssl/cert_validate_message.h"
21 #include "ssl/Config.h"
22 #include "ssl/helper.h"
23 #include "wordlist.h"
24
25 #include <limits>
26
27 Ssl::CertValidationHelper::CacheType *Ssl::CertValidationHelper::HelperCache = nullptr;
28
29 #if USE_SSL_CRTD
30
31 namespace Ssl {
32
33 /// Initiator of an Ssl::Helper query.
34 class GeneratorRequestor {
35 public:
36 GeneratorRequestor(HLPCB *aCallback, void *aData): callback(aCallback), data(aData) {}
37 HLPCB *callback;
38 CallbackData data;
39 };
40
41 /// A pending Ssl::Helper request, combining the original and collapsed queries.
42 class GeneratorRequest {
43 CBDATA_CLASS(GeneratorRequest);
44
45 public:
46 /// adds a GeneratorRequestor
47 void emplace(HLPCB *callback, void *data) { requestors.emplace_back(callback, data); }
48
49 SBuf query; ///< Ssl::Helper request message (GeneratorRequests key)
50
51 /// Ssl::Helper request initiators waiting for the same answer (FIFO).
52 typedef std::vector<GeneratorRequestor> GeneratorRequestors;
53 GeneratorRequestors requestors;
54 };
55
56 /// Ssl::Helper query:GeneratorRequest map
57 typedef std::unordered_map<SBuf, GeneratorRequest*> GeneratorRequests;
58
59 static void HandleGeneratorReply(void *data, const ::Helper::Reply &reply);
60
61 } // namespace Ssl
62
63 CBDATA_NAMESPACED_CLASS_INIT(Ssl, GeneratorRequest);
64
65 /// prints Ssl::GeneratorRequest for debugging
66 static std::ostream &
67 operator <<(std::ostream &os, const Ssl::GeneratorRequest &gr)
68 {
69 return os << "crtGenRq" << gr.query.id.value << "/" << gr.requestors.size();
70 }
71
72 /// pending Ssl::Helper requests (to all certificate generator helpers combined)
73 static Ssl::GeneratorRequests TheGeneratorRequests;
74
75 helper::Pointer Ssl::Helper::ssl_crtd = nullptr;
76
77 void Ssl::Helper::Init()
78 {
79 assert(ssl_crtd == nullptr);
80
81 // we need to start ssl_crtd only if some port(s) need to bump SSL *and* generate certificates
82 // TODO: generate host certificates for SNI enabled accel ports
83 bool found = false;
84 for (AnyP::PortCfgPointer s = HttpPortList; !found && s != nullptr; s = s->next)
85 found = s->flags.tunnelSslBumping && s->secure.generateHostCertificates;
86 if (!found)
87 return;
88
89 ssl_crtd = helper::Make("sslcrtd_program");
90 ssl_crtd->childs.updateLimits(Ssl::TheConfig.ssl_crtdChildren);
91 ssl_crtd->ipc_type = IPC_STREAM;
92 // The crtd messages may contain the eol ('\n') character. We are
93 // going to use the '\1' char as the end-of-message mark.
94 ssl_crtd->eom = '\1';
95 assert(ssl_crtd->cmdline == nullptr);
96 {
97 char *tmp = xstrdup(Ssl::TheConfig.ssl_crtd);
98 char *tmp_begin = tmp;
99 char *token = nullptr;
100 while ((token = strwordtok(nullptr, &tmp))) {
101 wordlistAdd(&ssl_crtd->cmdline, token);
102 }
103 safe_free(tmp_begin);
104 }
105 helperOpenServers(ssl_crtd);
106 }
107
108 void Ssl::Helper::Shutdown()
109 {
110 if (!ssl_crtd)
111 return;
112 helperShutdown(ssl_crtd);
113 wordlistDestroy(&ssl_crtd->cmdline);
114 ssl_crtd = nullptr;
115 }
116
117 void
118 Ssl::Helper::Reconfigure()
119 {
120 Shutdown();
121 Init();
122 }
123
124 void Ssl::Helper::Submit(CrtdMessage const & message, HLPCB * callback, void * data)
125 {
126 SBuf rawMessage(message.compose().c_str()); // XXX: helpers cannot use SBuf
127 rawMessage.append("\n", 1);
128
129 const auto pending = TheGeneratorRequests.find(rawMessage);
130 if (pending != TheGeneratorRequests.end()) {
131 pending->second->emplace(callback, data);
132 debugs(83, 5, "collapsed request from " << data << " onto " << *pending->second);
133 return;
134 }
135
136 GeneratorRequest *request = new GeneratorRequest;
137 request->query = rawMessage;
138 request->emplace(callback, data);
139 TheGeneratorRequests.emplace(request->query, request);
140 debugs(83, 5, "request from " << data << " as " << *request);
141 // ssl_crtd becomes nil if Squid is reconfigured without SslBump or
142 // certificate generation disabled in the new configuration
143 if (ssl_crtd && ssl_crtd->trySubmit(request->query.c_str(), HandleGeneratorReply, request))
144 return;
145
146 ::Helper::Reply failReply(::Helper::BrokenHelper);
147 failReply.notes.add("message", "error 45 Temporary network problem, please retry later");
148 HandleGeneratorReply(request, failReply);
149 }
150
151 /// receives helper response
152 static void
153 Ssl::HandleGeneratorReply(void *data, const ::Helper::Reply &reply)
154 {
155 const std::unique_ptr<Ssl::GeneratorRequest> request(static_cast<Ssl::GeneratorRequest*>(data));
156 assert(request);
157 const auto erased = TheGeneratorRequests.erase(request->query);
158 assert(erased);
159
160 for (auto &requestor: request->requestors) {
161 if (void *cbdata = requestor.data.validDone()) {
162 debugs(83, 5, "to " << cbdata << " in " << *request);
163 requestor.callback(cbdata, reply);
164 }
165 }
166 }
167 #endif //USE_SSL_CRTD
168
169 helper::Pointer Ssl::CertValidationHelper::ssl_crt_validator = nullptr;
170
171 void Ssl::CertValidationHelper::Init()
172 {
173 if (!Ssl::TheConfig.ssl_crt_validator)
174 return;
175
176 assert(ssl_crt_validator == nullptr);
177
178 // we need to start ssl_crtd only if some port(s) need to bump SSL
179 bool found = false;
180 for (AnyP::PortCfgPointer s = HttpPortList; !found && s != nullptr; s = s->next)
181 found = s->flags.tunnelSslBumping;
182 if (!found)
183 return;
184
185 ssl_crt_validator = helper::Make("ssl_crt_validator");
186 ssl_crt_validator->childs.updateLimits(Ssl::TheConfig.ssl_crt_validator_Children);
187 ssl_crt_validator->ipc_type = IPC_STREAM;
188 // The crtd messages may contain the eol ('\n') character. We are
189 // going to use the '\1' char as the end-of-message mark.
190 ssl_crt_validator->eom = '\1';
191 assert(ssl_crt_validator->cmdline == nullptr);
192
193 /* defaults */
194 int ttl = 3600; // 1 hour
195 size_t cache = 64*1024*1024; // 64 MB
196 {
197 // TODO: Do this during parseConfigFile() for proper parsing, error handling
198 char *tmp = xstrdup(Ssl::TheConfig.ssl_crt_validator);
199 char *tmp_begin = tmp;
200 char * token = nullptr;
201 bool parseParams = true;
202 while ((token = strwordtok(nullptr, &tmp))) {
203 if (parseParams) {
204 if (strcmp(token, "ttl=infinity") == 0) {
205 ttl = std::numeric_limits<CacheType::Ttl>::max();
206 continue;
207 } else if (strncmp(token, "ttl=", 4) == 0) {
208 ttl = xatoi(token + 4);
209 if (ttl < 0) {
210 throw TextException(ToSBuf("Negative TTL in sslcrtvalidator_program ", Ssl::TheConfig.ssl_crt_validator,
211 Debug::Extra, "For unlimited TTL, use ttl=infinity"),
212 Here());
213 }
214 continue;
215 } else if (strncmp(token, "cache=", 6) == 0) {
216 cache = xatoi(token + 6);
217 continue;
218 } else
219 parseParams = false;
220 }
221 wordlistAdd(&ssl_crt_validator->cmdline, token);
222 }
223 xfree(tmp_begin);
224 }
225 helperOpenServers(ssl_crt_validator);
226
227 //WARNING: initializing static member in an object initialization method
228 assert(HelperCache == nullptr);
229 HelperCache = new CacheType(cache, ttl);
230 }
231
232 void Ssl::CertValidationHelper::Shutdown()
233 {
234 if (!ssl_crt_validator)
235 return;
236 helperShutdown(ssl_crt_validator);
237 wordlistDestroy(&ssl_crt_validator->cmdline);
238 ssl_crt_validator = nullptr;
239
240 // CertValidationHelper::HelperCache is a static member, it is not good policy to
241 // reset it here. Will work because the current Ssl::CertValidationHelper is
242 // always the same static object.
243 delete HelperCache;
244 HelperCache = nullptr;
245 }
246
247 void
248 Ssl::CertValidationHelper::Reconfigure()
249 {
250 Shutdown();
251 Init();
252 }
253
254 class submitData
255 {
256 CBDATA_CLASS(submitData);
257
258 public:
259 SBuf query;
260 Ssl::CertValidationHelper::Callback callback;
261 Security::SessionPointer ssl;
262 };
263 CBDATA_CLASS_INIT(submitData);
264
265 static void
266 sslCrtvdHandleReplyWrapper(void *data, const ::Helper::Reply &reply)
267 {
268 Ssl::CertValidationMsg replyMsg(Ssl::CrtdMessage::REPLY);
269
270 submitData *crtdvdData = static_cast<submitData *>(data);
271 assert(crtdvdData->ssl.get());
272 Ssl::CertValidationResponse::Pointer validationResponse = new Ssl::CertValidationResponse(crtdvdData->ssl);
273 if (reply.result == ::Helper::BrokenHelper) {
274 debugs(83, DBG_IMPORTANT, "ERROR: \"ssl_crtvd\" helper error response: " << reply.other().content());
275 validationResponse->resultCode = ::Helper::BrokenHelper;
276 } else if (!reply.other().hasContent()) {
277 debugs(83, DBG_IMPORTANT, "\"ssl_crtvd\" helper returned NULL response");
278 validationResponse->resultCode = ::Helper::BrokenHelper;
279 } else if (replyMsg.parse(reply.other().content(), reply.other().contentSize()) != Ssl::CrtdMessage::OK ||
280 !replyMsg.parseResponse(*validationResponse) ) {
281 debugs(83, DBG_IMPORTANT, "WARNING: Reply from ssl_crtvd for " << " is incorrect");
282 debugs(83, DBG_IMPORTANT, "ERROR: Certificate cannot be validated. ssl_crtvd response: " << replyMsg.getBody());
283 validationResponse->resultCode = ::Helper::BrokenHelper;
284 } else
285 validationResponse->resultCode = reply.result;
286
287 crtdvdData->callback.answer() = validationResponse;
288 ScheduleCallHere(crtdvdData->callback.release());
289
290 if (Ssl::CertValidationHelper::HelperCache &&
291 (validationResponse->resultCode == ::Helper::Okay || validationResponse->resultCode == ::Helper::Error)) {
292 (void)Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query, validationResponse);
293 }
294
295 delete crtdvdData;
296 }
297
298 void
299 Ssl::CertValidationHelper::Submit(const Ssl::CertValidationRequest &request, const Callback &callback)
300 {
301 Ssl::CertValidationMsg message(Ssl::CrtdMessage::REQUEST);
302 message.setCode(Ssl::CertValidationMsg::code_cert_validate);
303 message.composeRequest(request);
304 debugs(83, 5, "SSL crtvd request: " << message.compose().c_str());
305
306 submitData *crtdvdData = new submitData;
307 crtdvdData->query.assign(message.compose().c_str());
308 crtdvdData->query.append('\n');
309 crtdvdData->callback = callback;
310 crtdvdData->ssl = request.ssl;
311 Ssl::CertValidationResponse::Pointer const*validationResponse;
312
313 if (CertValidationHelper::HelperCache &&
314 (validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query))) {
315
316 crtdvdData->callback.answer() = *validationResponse;
317 ScheduleCallHere(crtdvdData->callback.release());
318 delete crtdvdData;
319 return;
320 }
321
322 // ssl_crt_validator becomes nil if Squid is reconfigured with cert
323 // validator disabled in the new configuration
324 if (ssl_crt_validator && ssl_crt_validator->trySubmit(crtdvdData->query.c_str(), sslCrtvdHandleReplyWrapper, crtdvdData))
325 return;
326
327 Ssl::CertValidationResponse::Pointer resp = new Ssl::CertValidationResponse(crtdvdData->ssl);
328 resp->resultCode = ::Helper::BrokenHelper;
329 crtdvdData->callback.answer() = resp;
330 ScheduleCallHere(crtdvdData->callback.release());
331 delete crtdvdData;
332 return;
333 }
334