2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
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.
10 #include "../helper.h"
11 #include "anyp/PortCfg.h"
12 #include "base/AsyncCallbacks.h"
15 #include "helper/Reply.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"
27 Ssl::CertValidationHelper::CacheType
*Ssl::CertValidationHelper::HelperCache
= nullptr;
33 /// Initiator of an Ssl::Helper query.
34 class GeneratorRequestor
{
36 GeneratorRequestor(HLPCB
*aCallback
, void *aData
): callback(aCallback
), data(aData
) {}
41 /// A pending Ssl::Helper request, combining the original and collapsed queries.
42 class GeneratorRequest
{
43 CBDATA_CLASS(GeneratorRequest
);
46 /// adds a GeneratorRequestor
47 void emplace(HLPCB
*callback
, void *data
) { requestors
.emplace_back(callback
, data
); }
49 SBuf query
; ///< Ssl::Helper request message (GeneratorRequests key)
51 /// Ssl::Helper request initiators waiting for the same answer (FIFO).
52 typedef std::vector
<GeneratorRequestor
> GeneratorRequestors
;
53 GeneratorRequestors requestors
;
56 /// Ssl::Helper query:GeneratorRequest map
57 typedef std::unordered_map
<SBuf
, GeneratorRequest
*> GeneratorRequests
;
59 static void HandleGeneratorReply(void *data
, const ::Helper::Reply
&reply
);
63 CBDATA_NAMESPACED_CLASS_INIT(Ssl
, GeneratorRequest
);
65 /// prints Ssl::GeneratorRequest for debugging
67 operator <<(std::ostream
&os
, const Ssl::GeneratorRequest
&gr
)
69 return os
<< "crtGenRq" << gr
.query
.id
.value
<< "/" << gr
.requestors
.size();
72 /// pending Ssl::Helper requests (to all certificate generator helpers combined)
73 static Ssl::GeneratorRequests TheGeneratorRequests
;
75 helper::Pointer
Ssl::Helper::ssl_crtd
= nullptr;
77 void Ssl::Helper::Init()
79 assert(ssl_crtd
== nullptr);
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
84 for (AnyP::PortCfgPointer s
= HttpPortList
; !found
&& s
!= nullptr; s
= s
->next
)
85 found
= s
->flags
.tunnelSslBumping
&& s
->secure
.generateHostCertificates
;
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.
95 assert(ssl_crtd
->cmdline
== nullptr);
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
);
103 safe_free(tmp_begin
);
105 helperOpenServers(ssl_crtd
);
108 void Ssl::Helper::Shutdown()
112 helperShutdown(ssl_crtd
);
113 wordlistDestroy(&ssl_crtd
->cmdline
);
118 Ssl::Helper::Reconfigure()
124 void Ssl::Helper::Submit(CrtdMessage
const & message
, HLPCB
* callback
, void * data
)
126 SBuf
rawMessage(message
.compose().c_str()); // XXX: helpers cannot use SBuf
127 rawMessage
.append("\n", 1);
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
);
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
))
146 ::Helper::Reply
failReply(::Helper::BrokenHelper
);
147 failReply
.notes
.add("message", "error 45 Temporary network problem, please retry later");
148 HandleGeneratorReply(request
, failReply
);
151 /// receives helper response
153 Ssl::HandleGeneratorReply(void *data
, const ::Helper::Reply
&reply
)
155 const std::unique_ptr
<Ssl::GeneratorRequest
> request(static_cast<Ssl::GeneratorRequest
*>(data
));
157 const auto erased
= TheGeneratorRequests
.erase(request
->query
);
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
);
167 #endif //USE_SSL_CRTD
169 helper::Pointer
Ssl::CertValidationHelper::ssl_crt_validator
= nullptr;
171 void Ssl::CertValidationHelper::Init()
173 if (!Ssl::TheConfig
.ssl_crt_validator
)
176 assert(ssl_crt_validator
== nullptr);
178 // we need to start ssl_crtd only if some port(s) need to bump SSL
180 for (AnyP::PortCfgPointer s
= HttpPortList
; !found
&& s
!= nullptr; s
= s
->next
)
181 found
= s
->flags
.tunnelSslBumping
;
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);
194 int ttl
= 3600; // 1 hour
195 size_t cache
= 64*1024*1024; // 64 MB
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
))) {
204 if (strcmp(token
, "ttl=infinity") == 0) {
205 ttl
= std::numeric_limits
<CacheType::Ttl
>::max();
207 } else if (strncmp(token
, "ttl=", 4) == 0) {
208 ttl
= xatoi(token
+ 4);
210 throw TextException(ToSBuf("Negative TTL in sslcrtvalidator_program ", Ssl::TheConfig
.ssl_crt_validator
,
211 Debug::Extra
, "For unlimited TTL, use ttl=infinity"),
215 } else if (strncmp(token
, "cache=", 6) == 0) {
216 cache
= xatoi(token
+ 6);
221 wordlistAdd(&ssl_crt_validator
->cmdline
, token
);
225 helperOpenServers(ssl_crt_validator
);
227 //WARNING: initializing static member in an object initialization method
228 assert(HelperCache
== nullptr);
229 HelperCache
= new CacheType(cache
, ttl
);
232 void Ssl::CertValidationHelper::Shutdown()
234 if (!ssl_crt_validator
)
236 helperShutdown(ssl_crt_validator
);
237 wordlistDestroy(&ssl_crt_validator
->cmdline
);
238 ssl_crt_validator
= nullptr;
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.
244 HelperCache
= nullptr;
248 Ssl::CertValidationHelper::Reconfigure()
256 CBDATA_CLASS(submitData
);
260 Ssl::CertValidationHelper::Callback callback
;
261 Security::SessionPointer ssl
;
263 CBDATA_CLASS_INIT(submitData
);
266 sslCrtvdHandleReplyWrapper(void *data
, const ::Helper::Reply
&reply
)
268 Ssl::CertValidationMsg
replyMsg(Ssl::CrtdMessage::REPLY
);
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
;
285 validationResponse
->resultCode
= reply
.result
;
287 crtdvdData
->callback
.answer() = validationResponse
;
288 ScheduleCallHere(crtdvdData
->callback
.release());
290 if (Ssl::CertValidationHelper::HelperCache
&&
291 (validationResponse
->resultCode
== ::Helper::Okay
|| validationResponse
->resultCode
== ::Helper::Error
)) {
292 (void)Ssl::CertValidationHelper::HelperCache
->add(crtdvdData
->query
, validationResponse
);
299 Ssl::CertValidationHelper::Submit(const Ssl::CertValidationRequest
&request
, const Callback
&callback
)
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());
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
;
313 if (CertValidationHelper::HelperCache
&&
314 (validationResponse
= CertValidationHelper::HelperCache
->get(crtdvdData
->query
))) {
316 crtdvdData
->callback
.answer() = *validationResponse
;
317 ScheduleCallHere(crtdvdData
->callback
.release());
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
))
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());