2 * Copyright (C) 1996-2025 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 "ssl/cert_validate_message.h"
20 #include "ssl/Config.h"
21 #include "ssl/helper.h"
26 Ssl::CertValidationHelper::CacheType
*Ssl::CertValidationHelper::HelperCache
= nullptr;
32 /// Initiator of an Ssl::Helper query.
33 class GeneratorRequestor
{
35 GeneratorRequestor(HLPCB
*aCallback
, void *aData
): callback(aCallback
), data(aData
) {}
40 /// A pending Ssl::Helper request, combining the original and collapsed queries.
41 class GeneratorRequest
{
42 CBDATA_CLASS(GeneratorRequest
);
45 /// adds a GeneratorRequestor
46 void emplace(HLPCB
*callback
, void *data
) { requestors
.emplace_back(callback
, data
); }
48 SBuf query
; ///< Ssl::Helper request message (GeneratorRequests key)
50 /// Ssl::Helper request initiators waiting for the same answer (FIFO).
51 typedef std::vector
<GeneratorRequestor
> GeneratorRequestors
;
52 GeneratorRequestors requestors
;
55 /// Ssl::Helper query:GeneratorRequest map
56 typedef std::unordered_map
<SBuf
, GeneratorRequest
*> GeneratorRequests
;
58 static void HandleGeneratorReply(void *data
, const ::Helper::Reply
&reply
);
60 /// pending Ssl::Helper requests (to all certificate generator helpers combined)
61 static GeneratorRequests
&
62 TheGeneratorRequests()
64 static auto generatorRequests
= new GeneratorRequests();
65 return *generatorRequests
;
70 CBDATA_NAMESPACED_CLASS_INIT(Ssl
, GeneratorRequest
);
72 /// prints Ssl::GeneratorRequest for debugging
74 operator <<(std::ostream
&os
, const Ssl::GeneratorRequest
&gr
)
76 return os
<< "crtGenRq" << gr
.query
.id
.value
<< "/" << gr
.requestors
.size();
79 Helper::Client::Pointer
Ssl::Helper::ssl_crtd
;
81 void Ssl::Helper::Init()
83 assert(ssl_crtd
== nullptr);
85 // we need to start ssl_crtd only if some port(s) need to bump SSL *and* generate certificates
86 // TODO: generate host certificates for SNI enabled accel ports
88 for (AnyP::PortCfgPointer s
= HttpPortList
; !found
&& s
!= nullptr; s
= s
->next
)
89 found
= s
->flags
.tunnelSslBumping
&& s
->secure
.generateHostCertificates
;
93 ssl_crtd
= ::Helper::Client::Make("sslcrtd_program");
94 ssl_crtd
->childs
.updateLimits(Ssl::TheConfig
.ssl_crtdChildren
);
95 ssl_crtd
->ipc_type
= IPC_STREAM
;
96 // The crtd messages may contain the eol ('\n') character. We are
97 // going to use the '\1' char as the end-of-message mark.
99 assert(ssl_crtd
->cmdline
== nullptr);
101 char *tmp
= xstrdup(Ssl::TheConfig
.ssl_crtd
);
102 char *tmp_begin
= tmp
;
103 char *token
= nullptr;
104 while ((token
= strwordtok(nullptr, &tmp
))) {
105 wordlistAdd(&ssl_crtd
->cmdline
, token
);
107 safe_free(tmp_begin
);
109 ssl_crtd
->openSessions();
112 void Ssl::Helper::Shutdown()
116 helperShutdown(ssl_crtd
);
117 wordlistDestroy(&ssl_crtd
->cmdline
);
122 Ssl::Helper::Reconfigure()
128 void Ssl::Helper::Submit(CrtdMessage
const & message
, HLPCB
* callback
, void * data
)
130 SBuf
rawMessage(message
.compose().c_str()); // XXX: helpers cannot use SBuf
131 rawMessage
.append("\n", 1);
133 const auto pending
= TheGeneratorRequests().find(rawMessage
);
134 if (pending
!= TheGeneratorRequests().end()) {
135 pending
->second
->emplace(callback
, data
);
136 debugs(83, 5, "collapsed request from " << data
<< " onto " << *pending
->second
);
140 GeneratorRequest
*request
= new GeneratorRequest
;
141 request
->query
= rawMessage
;
142 request
->emplace(callback
, data
);
143 TheGeneratorRequests().emplace(request
->query
, request
);
144 debugs(83, 5, "request from " << data
<< " as " << *request
);
145 // ssl_crtd becomes nil if Squid is reconfigured without SslBump or
146 // certificate generation disabled in the new configuration
147 if (ssl_crtd
&& ssl_crtd
->trySubmit(request
->query
.c_str(), HandleGeneratorReply
, request
))
150 ::Helper::Reply
failReply(::Helper::BrokenHelper
);
151 failReply
.notes
.add("message", "error 45 Temporary network problem, please retry later");
152 HandleGeneratorReply(request
, failReply
);
155 /// receives helper response
157 Ssl::HandleGeneratorReply(void *data
, const ::Helper::Reply
&reply
)
159 const std::unique_ptr
<Ssl::GeneratorRequest
> request(static_cast<Ssl::GeneratorRequest
*>(data
));
161 const auto erased
= TheGeneratorRequests().erase(request
->query
);
164 for (auto &requestor
: request
->requestors
) {
165 if (void *cbdata
= requestor
.data
.validDone()) {
166 debugs(83, 5, "to " << cbdata
<< " in " << *request
);
167 requestor
.callback(cbdata
, reply
);
171 #endif //USE_SSL_CRTD
173 Helper::Client::Pointer
Ssl::CertValidationHelper::ssl_crt_validator
;
175 void Ssl::CertValidationHelper::Init()
177 if (!Ssl::TheConfig
.ssl_crt_validator
)
180 assert(ssl_crt_validator
== nullptr);
182 // we need to start ssl_crtd only if some port(s) need to bump SSL
184 for (AnyP::PortCfgPointer s
= HttpPortList
; !found
&& s
!= nullptr; s
= s
->next
)
185 found
= s
->flags
.tunnelSslBumping
;
189 ssl_crt_validator
= ::Helper::Client::Make("ssl_crt_validator");
190 ssl_crt_validator
->childs
.updateLimits(Ssl::TheConfig
.ssl_crt_validator_Children
);
191 ssl_crt_validator
->ipc_type
= IPC_STREAM
;
192 // The crtd messages may contain the eol ('\n') character. We are
193 // going to use the '\1' char as the end-of-message mark.
194 ssl_crt_validator
->eom
= '\1';
195 assert(ssl_crt_validator
->cmdline
== nullptr);
198 int ttl
= 3600; // 1 hour
199 size_t cache
= 64*1024*1024; // 64 MB
201 // TODO: Do this during parseConfigFile() for proper parsing, error handling
202 char *tmp
= xstrdup(Ssl::TheConfig
.ssl_crt_validator
);
203 char *tmp_begin
= tmp
;
204 char * token
= nullptr;
205 bool parseParams
= true;
206 while ((token
= strwordtok(nullptr, &tmp
))) {
208 if (strcmp(token
, "ttl=infinity") == 0) {
209 ttl
= std::numeric_limits
<CacheType::Ttl
>::max();
211 } else if (strncmp(token
, "ttl=", 4) == 0) {
212 ttl
= xatoi(token
+ 4);
214 throw TextException(ToSBuf("Negative TTL in sslcrtvalidator_program ", Ssl::TheConfig
.ssl_crt_validator
,
215 Debug::Extra
, "For unlimited TTL, use ttl=infinity"),
219 } else if (strncmp(token
, "cache=", 6) == 0) {
220 cache
= xatoi(token
+ 6);
225 wordlistAdd(&ssl_crt_validator
->cmdline
, token
);
229 ssl_crt_validator
->openSessions();
231 //WARNING: initializing static member in an object initialization method
232 assert(HelperCache
== nullptr);
233 HelperCache
= new CacheType(cache
, ttl
);
236 void Ssl::CertValidationHelper::Shutdown()
238 if (!ssl_crt_validator
)
240 helperShutdown(ssl_crt_validator
);
241 wordlistDestroy(&ssl_crt_validator
->cmdline
);
242 ssl_crt_validator
= nullptr;
244 // CertValidationHelper::HelperCache is a static member, it is not good policy to
245 // reset it here. Will work because the current Ssl::CertValidationHelper is
246 // always the same static object.
248 HelperCache
= nullptr;
252 Ssl::CertValidationHelper::Reconfigure()
260 CBDATA_CLASS(submitData
);
264 Ssl::CertValidationHelper::Callback callback
;
265 Security::SessionPointer ssl
;
267 CBDATA_CLASS_INIT(submitData
);
270 sslCrtvdHandleReplyWrapper(void *data
, const ::Helper::Reply
&reply
)
272 Ssl::CertValidationMsg
replyMsg(Ssl::CrtdMessage::REPLY
);
274 submitData
*crtdvdData
= static_cast<submitData
*>(data
);
275 assert(crtdvdData
->ssl
.get());
276 Ssl::CertValidationResponse::Pointer validationResponse
= new Ssl::CertValidationResponse(crtdvdData
->ssl
);
277 if (reply
.result
== ::Helper::BrokenHelper
) {
278 debugs(83, DBG_IMPORTANT
, "ERROR: \"ssl_crtvd\" helper error response: " << reply
.other().content());
279 validationResponse
->resultCode
= ::Helper::BrokenHelper
;
280 } else if (!reply
.other().hasContent()) {
281 debugs(83, DBG_IMPORTANT
, "\"ssl_crtvd\" helper returned NULL response");
282 validationResponse
->resultCode
= ::Helper::BrokenHelper
;
283 } else if (replyMsg
.parse(reply
.other().content(), reply
.other().contentSize()) != Ssl::CrtdMessage::OK
||
284 !replyMsg
.parseResponse(*validationResponse
) ) {
285 debugs(83, DBG_IMPORTANT
, "WARNING: Reply from ssl_crtvd for " << " is incorrect");
286 debugs(83, DBG_IMPORTANT
, "ERROR: Certificate cannot be validated. ssl_crtvd response: " << replyMsg
.getBody());
287 validationResponse
->resultCode
= ::Helper::BrokenHelper
;
289 validationResponse
->resultCode
= reply
.result
;
291 crtdvdData
->callback
.answer() = validationResponse
;
292 ScheduleCallHere(crtdvdData
->callback
.release());
294 if (Ssl::CertValidationHelper::HelperCache
&&
295 (validationResponse
->resultCode
== ::Helper::Okay
|| validationResponse
->resultCode
== ::Helper::Error
)) {
296 (void)Ssl::CertValidationHelper::HelperCache
->add(crtdvdData
->query
, validationResponse
);
303 Ssl::CertValidationHelper::Submit(const Ssl::CertValidationRequest
&request
, const Callback
&callback
)
305 Ssl::CertValidationMsg
message(Ssl::CrtdMessage::REQUEST
);
306 message
.setCode(Ssl::CertValidationMsg::code_cert_validate
);
307 message
.composeRequest(request
);
308 debugs(83, 5, "SSL crtvd request: " << message
.compose().c_str());
310 submitData
*crtdvdData
= new submitData
;
311 crtdvdData
->query
.assign(message
.compose().c_str());
312 crtdvdData
->query
.append('\n');
313 crtdvdData
->callback
= callback
;
314 crtdvdData
->ssl
= request
.ssl
;
315 Ssl::CertValidationResponse::Pointer
const*validationResponse
;
317 if (CertValidationHelper::HelperCache
&&
318 (validationResponse
= CertValidationHelper::HelperCache
->get(crtdvdData
->query
))) {
320 crtdvdData
->callback
.answer() = *validationResponse
;
321 ScheduleCallHere(crtdvdData
->callback
.release());
326 // ssl_crt_validator becomes nil if Squid is reconfigured with cert
327 // validator disabled in the new configuration
328 if (ssl_crt_validator
&& ssl_crt_validator
->trySubmit(crtdvdData
->query
.c_str(), sslCrtvdHandleReplyWrapper
, crtdvdData
))
331 Ssl::CertValidationResponse::Pointer resp
= new Ssl::CertValidationResponse(crtdvdData
->ssl
);
332 resp
->resultCode
= ::Helper::BrokenHelper
;
333 crtdvdData
->callback
.answer() = resp
;
334 ScheduleCallHere(crtdvdData
->callback
.release());