]> git.ipfire.org Git - thirdparty/squid.git/blob - src/security/PeerConnector.cc
Maintenance: Removed most NULLs using modernize-use-nullptr (#1075)
[thirdparty/squid.git] / src / security / PeerConnector.cc
1 /*
2 * Copyright (C) 1996-2022 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 /* DEBUG: section 83 TLS Server/Peer negotiation */
10
11 #include "squid.h"
12 #include "acl/FilledChecklist.h"
13 #include "base/IoManip.h"
14 #include "comm/Loops.h"
15 #include "comm/Read.h"
16 #include "Downloader.h"
17 #include "errorpage.h"
18 #include "fde.h"
19 #include "FwdState.h"
20 #include "http/Stream.h"
21 #include "HttpRequest.h"
22 #include "neighbors.h"
23 #include "pconn.h"
24 #include "security/Io.h"
25 #include "security/Certificate.h"
26 #include "security/NegotiationHistory.h"
27 #include "security/PeerConnector.h"
28 #include "SquidConfig.h"
29 #if USE_OPENSSL
30 #include "ssl/bio.h"
31 #include "ssl/cert_validate_message.h"
32 #include "ssl/Config.h"
33 #include "ssl/helper.h"
34 #endif
35
36 CBDATA_NAMESPACED_CLASS_INIT(Security, PeerConnector);
37
38 Security::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const AccessLogEntryPointer &alp, const time_t timeout) :
39 AsyncJob("Security::PeerConnector"),
40 noteFwdPconnUse(false),
41 serverConn(aServerConn),
42 al(alp),
43 callback(aCallback),
44 negotiationTimeout(timeout),
45 startTime(squid_curtime),
46 useCertValidator_(true),
47 certsDownloads(0)
48 {
49 debugs(83, 5, serverConn);
50
51 // if this throws, the caller's cb dialer is not our CbDialer
52 Must(dynamic_cast<CbDialer*>(callback->getDialer()));
53
54 // watch for external connection closures
55 Must(Comm::IsConnOpen(serverConn));
56 Must(!fd_table[serverConn->fd].closing());
57 typedef CommCbMemFunT<Security::PeerConnector, CommCloseCbParams> Dialer;
58 closeHandler = JobCallback(9, 5, Dialer, this, Security::PeerConnector::commCloseHandler);
59 comm_add_close_handler(serverConn->fd, closeHandler);
60 }
61
62 Security::PeerConnector::~PeerConnector() = default;
63
64 bool Security::PeerConnector::doneAll() const
65 {
66 return (!callback || callback->canceled()) && AsyncJob::doneAll();
67 }
68
69 /// Preps connection and SSL state. Calls negotiate().
70 void
71 Security::PeerConnector::start()
72 {
73 AsyncJob::start();
74 debugs(83, 5, "this=" << (void*)this);
75
76 // we own this Comm::Connection object and its fd exclusively, but must bail
77 // if others started closing the socket while we were waiting to start()
78 assert(Comm::IsConnOpen(serverConn));
79 if (fd_table[serverConn->fd].closing()) {
80 bail(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al));
81 return;
82 }
83
84 Security::SessionPointer tmp;
85 if (initialize(tmp))
86 negotiate();
87 else
88 mustStop("Security::PeerConnector TLS socket initialize failed");
89 }
90
91 void
92 Security::PeerConnector::fillChecklist(ACLFilledChecklist &checklist) const
93 {
94 if (!checklist.al)
95 checklist.al = al;
96 checklist.syncAle(request.getRaw(), nullptr);
97 // checklist.fd(fd); XXX: need client FD here
98
99 #if USE_OPENSSL
100 if (!checklist.serverCert) {
101 if (const auto session = fd_table[serverConnection()->fd].ssl.get())
102 checklist.serverCert.resetWithoutLocking(SSL_get_peer_certificate(session));
103 }
104 #else
105 // checklist.serverCert is not maintained in other builds
106 #endif
107 }
108
109 void
110 Security::PeerConnector::commCloseHandler(const CommCloseCbParams &params)
111 {
112 debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data);
113
114 closeHandler = nullptr;
115 if (serverConn) {
116 countFailingConnection();
117 serverConn->noteClosure();
118 serverConn = nullptr;
119 }
120
121 const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
122 static const auto d = MakeNamedErrorDetail("TLS_CONNECT_CLOSE");
123 err->detailError(d);
124 bail(err);
125 }
126
127 void
128 Security::PeerConnector::commTimeoutHandler(const CommTimeoutCbParams &)
129 {
130 debugs(83, 5, serverConnection() << " timedout. this=" << (void*)this);
131 const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scGatewayTimeout, request.getRaw(), al);
132 static const auto d = MakeNamedErrorDetail("TLS_CONNECT_TIMEOUT");
133 err->detailError(d);
134 bail(err);
135 }
136
137 bool
138 Security::PeerConnector::initialize(Security::SessionPointer &serverSession)
139 {
140 Must(Comm::IsConnOpen(serverConnection()));
141
142 Security::ContextPointer ctx(getTlsContext());
143 debugs(83, 5, serverConnection() << ", ctx=" << (void*)ctx.get());
144
145 if (!ctx || !Security::CreateClientSession(ctx, serverConnection(), "server https start")) {
146 const auto xerrno = errno;
147 if (!ctx) {
148 debugs(83, DBG_IMPORTANT, "ERROR: initializing TLS connection: No security context.");
149 } // else CreateClientSession() did the appropriate debugs() already
150 const auto anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw(), al);
151 anErr->xerrno = xerrno;
152 noteNegotiationDone(anErr);
153 bail(anErr);
154 return false;
155 }
156
157 // A TLS/SSL session has now been created for the connection and stored in fd_table
158 serverSession = fd_table[serverConnection()->fd].ssl;
159 debugs(83, 5, serverConnection() << ", session=" << (void*)serverSession.get());
160
161 #if USE_OPENSSL
162 // If CertValidation Helper used do not lookup checklist for errors,
163 // but keep a list of errors to send it to CertValidator
164 if (!Ssl::TheConfig.ssl_crt_validator) {
165 // Create the ACL check list now, while we have access to more info.
166 // The list is used in ssl_verify_cb() and is freed in ssl_free().
167 // XXX: This info may change, especially if we fetch missing certs.
168 // TODO: Remove ACLFilledChecklist::sslErrors and other pre-computed
169 // state in favor of the ACLs accessing current/fresh info directly.
170 if (acl_access *acl = ::Config.ssl_client.cert_error) {
171 ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
172 fillChecklist(*check);
173 SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check);
174 }
175 }
176
177 // Protect from cycles in the certificate dependency graph: TLS site S1 is
178 // missing certificate C1 located at TLS site S2. TLS site S2 is missing
179 // certificate C2 located at [...] TLS site S1.
180 const auto cycle = certDownloadNestingLevel() >= MaxNestedDownloads;
181 if (cycle)
182 debugs(83, 3, "will not fetch any missing certificates; suspecting cycle: " << certDownloadNestingLevel() << '/' << MaxNestedDownloads);
183 const auto sessData = Ssl::VerifyCallbackParameters::New(*serverSession);
184 // when suspecting a cycle, break it by not fetching any missing certs
185 sessData->callerHandlesMissingCertificates = !cycle;
186 #endif
187
188 return true;
189 }
190
191 void
192 Security::PeerConnector::recordNegotiationDetails()
193 {
194 Must(Comm::IsConnOpen(serverConnection()));
195
196 const int fd = serverConnection()->fd;
197 Security::SessionPointer session(fd_table[fd].ssl);
198
199 // retrieve TLS server negotiated information if any
200 serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session);
201
202 #if USE_OPENSSL
203 // retrieve TLS parsed extra info
204 BIO *b = SSL_get_rbio(session.get());
205 Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
206 if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails())
207 serverConnection()->tlsNegotiations()->retrieveParsedInfo(details);
208 #endif
209 }
210
211 void
212 Security::PeerConnector::negotiate()
213 {
214 Must(Comm::IsConnOpen(serverConnection()));
215
216 const int fd = serverConnection()->fd;
217 if (fd_table[fd].closing())
218 return;
219
220 const auto result = Security::Connect(*serverConnection());
221
222 #if USE_OPENSSL
223 auto &sconn = *fd_table[fd].ssl;
224
225 // log ASAP, even if the handshake has not completed (or failed)
226 keyLogger.checkpoint(sconn, *this);
227
228 // OpenSSL v1 APIs do not allow unthreaded applications like Squid to fetch
229 // missing certificates _during_ OpenSSL certificate validation. Our
230 // handling of X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY (abbreviated
231 // here as EUNABLE) approximates what would happen if we did (attempt to)
232 // fetch any missing certificates during OpenSSL certificate validation.
233 // * We did not hide EUNABLE; SSL_connect() was successful: Handle success.
234 // * We did not hide EUNABLE; SSL_connect() reported some error E: Honor E.
235 // * We hid EUNABLE; SSL_connect() was successful: Remember success and try
236 // to fetch the missing certificates. If all goes well, honor success.
237 // * We hid EUNABLE; SSL_connect() reported EUNABLE: Warn but honor EUNABLE.
238 // * We hid EUNABLE; SSL_connect() reported some EOTHER: Remember EOTHER and
239 // try to fetch the missing certificates. If all goes well, honor EOTHER.
240 // If fetching or post-fetching validation fails, then honor that failure
241 // because EOTHER would not have happened if we fetched during validation.
242 if (auto &hidMissingIssuer = Ssl::VerifyCallbackParameters::At(sconn).hidMissingIssuer) {
243 hidMissingIssuer = false; // prep for the next SSL_connect()
244
245 if (result.category == IoResult::ioSuccess ||
246 !(result.errorDetail && result.errorDetail->errorNo() == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY))
247 return handleMissingCertificates(result);
248
249 debugs(83, DBG_IMPORTANT, "ERROR: Squid BUG: Honoring unexpected SSL_connect() failure: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY");
250 // fall through to regular error handling
251 }
252 #endif
253
254 handleNegotiationResult(result);
255 }
256
257 void
258 Security::PeerConnector::handleNegotiationResult(const Security::IoResult &result)
259 {
260 switch (result.category) {
261 case Security::IoResult::ioSuccess:
262 recordNegotiationDetails();
263 if (sslFinalized() && callback)
264 sendSuccess();
265 return; // we may be gone by now
266
267 case Security::IoResult::ioWantRead:
268 noteWantRead();
269 return;
270
271 case Security::IoResult::ioWantWrite:
272 noteWantWrite();
273 return;
274
275 case Security::IoResult::ioError:
276 break; // fall through to error handling
277 }
278
279 // TODO: Honor result.important when working in a reverse proxy role?
280 debugs(83, 2, "ERROR: Cannot establish a TLS connection to " << serverConnection() << ':' <<
281 Debug::Extra << "problem: " << result.errorDescription <<
282 RawPointer("detail: ", result.errorDetail).asExtra());
283 recordNegotiationDetails();
284 noteNegotiationError(result.errorDetail);
285 }
286
287 bool
288 Security::PeerConnector::sslFinalized()
289 {
290 #if USE_OPENSSL
291 if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
292 Must(Comm::IsConnOpen(serverConnection()));
293 const int fd = serverConnection()->fd;
294 Security::SessionPointer session(fd_table[fd].ssl);
295
296 Ssl::CertValidationRequest validationRequest;
297 // WARNING: Currently we do not use any locking for 'errors' member
298 // of the Ssl::CertValidationRequest class. In this code the
299 // Ssl::CertValidationRequest object used only to pass data to
300 // Ssl::CertValidationHelper::submit method.
301 validationRequest.ssl = session;
302 if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server))
303 validationRequest.domainName = dName->c_str();
304 if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)))
305 // validationRequest disappears on return so no need to cbdataReference
306 validationRequest.errors = errs;
307 try {
308 debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
309 AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr));
310 Ssl::CertValidationHelper::Submit(validationRequest, call);
311 return false;
312 } catch (const std::exception &e) {
313 debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
314 "request for " << validationRequest.domainName <<
315 " certificate: " << e.what() << "; will now block to " <<
316 "validate that certificate.");
317 // fall through to do blocking in-process generation.
318 const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
319
320 noteNegotiationDone(anErr);
321 bail(anErr);
322 return true;
323 }
324 }
325 #endif
326
327 noteNegotiationDone(nullptr);
328 return true;
329 }
330
331 #if USE_OPENSSL
332 void
333 Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointer validationResponse)
334 {
335 Must(validationResponse != nullptr);
336 Must(Comm::IsConnOpen(serverConnection()));
337
338 ErrorDetail::Pointer errDetails;
339 bool validatorFailed = false;
340
341 if (Debug::Enabled(83, 5)) {
342 Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl);
343 SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server));
344 debugs(83, 5, "cert validation result: " << validationResponse->resultCode << RawPointer(" host: ", server));
345 }
346
347 if (validationResponse->resultCode == ::Helper::Error) {
348 if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
349 Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
350 Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors));
351 SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs);
352 delete oldErrs;
353 }
354 } else if (validationResponse->resultCode != ::Helper::Okay)
355 validatorFailed = true;
356
357 if (!errDetails && !validatorFailed) {
358 noteNegotiationDone(nullptr);
359 if (callback)
360 sendSuccess();
361 return;
362 }
363
364 ErrorState *anErr = nullptr;
365 if (validatorFailed) {
366 anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
367 } else {
368 anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
369 anErr->detailError(errDetails);
370 /*anErr->xerrno= Should preserved*/
371 }
372
373 noteNegotiationDone(anErr);
374 bail(anErr);
375 return;
376 }
377 #endif
378
379 #if USE_OPENSSL
380 /// Checks errors in the cert. validator response against sslproxy_cert_error.
381 /// The first honored error, if any, is returned via errDetails parameter.
382 /// The method returns all seen errors except SSL_ERROR_NONE as Security::CertErrors.
383 Security::CertErrors *
384 Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, ErrorDetail::Pointer &errDetails)
385 {
386 Must(Comm::IsConnOpen(serverConnection()));
387
388 ACLFilledChecklist *check = nullptr;
389 Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
390
391 if (acl_access *acl = ::Config.ssl_client.cert_error) {
392 check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
393 fillChecklist(*check);
394 }
395
396 Security::CertErrors *errs = nullptr;
397 typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
398 for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
399 debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
400
401 assert(i->error_no != SSL_ERROR_NONE);
402
403 if (!errDetails) {
404 bool allowed = false;
405 if (check) {
406 check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
407 if (check->fastCheck().allowed())
408 allowed = true;
409 }
410 // else the Config.ssl_client.cert_error access list is not defined
411 // and the first error will cause the error page
412
413 if (allowed) {
414 debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer");
415 } else {
416 debugs(83, 5, "confirming SSL error " << i->error_no);
417 const auto &brokenCert = i->cert;
418 Security::CertPointer peerCert(SSL_get_peer_certificate(session.get()));
419 const char *aReason = i->error_reason.empty() ? nullptr : i->error_reason.c_str();
420 errDetails = new ErrorDetail(i->error_no, peerCert, brokenCert, aReason);
421 }
422 if (check) {
423 delete check->sslErrors;
424 check->sslErrors = nullptr;
425 }
426 }
427
428 if (!errs)
429 errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
430 else
431 errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth));
432 }
433 if (check)
434 delete check;
435
436 return errs;
437 }
438 #endif
439
440 /// A wrapper for Comm::SetSelect() notifications.
441 void
442 Security::PeerConnector::NegotiateSsl(int, void *data)
443 {
444 const auto pc = static_cast<PeerConnector::Pointer*>(data);
445 if (pc->valid())
446 (*pc)->negotiateSsl();
447 delete pc;
448 }
449
450 /// Comm::SetSelect() callback. Direct calls tickle/resume negotiations.
451 void
452 Security::PeerConnector::negotiateSsl()
453 {
454 // Use job calls to add done() checks and other job logic/protections.
455 CallJobHere(83, 7, this, Security::PeerConnector, negotiate);
456 }
457
458 void
459 Security::PeerConnector::noteWantRead()
460 {
461 debugs(83, 5, serverConnection());
462
463 Must(Comm::IsConnOpen(serverConnection()));
464 const int fd = serverConnection()->fd;
465
466 // read timeout to avoid getting stuck while reading from a silent server
467 typedef CommCbMemFunT<Security::PeerConnector, CommTimeoutCbParams> TimeoutDialer;
468 AsyncCall::Pointer timeoutCall = JobCallback(83, 5,
469 TimeoutDialer, this, Security::PeerConnector::commTimeoutHandler);
470 const auto timeout = Comm::MortalReadTimeout(startTime, negotiationTimeout);
471 commSetConnTimeout(serverConnection(), timeout, timeoutCall);
472
473 Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, new Pointer(this), 0);
474 }
475
476 void
477 Security::PeerConnector::noteWantWrite()
478 {
479 debugs(83, 5, serverConnection());
480 Must(Comm::IsConnOpen(serverConnection()));
481
482 const int fd = serverConnection()->fd;
483 Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, new Pointer(this), 0);
484 return;
485 }
486
487 void
488 Security::PeerConnector::noteNegotiationError(const Security::ErrorDetailPointer &detail)
489 {
490 const auto anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request, al);
491 if (detail) {
492 anErr->xerrno = detail->sysError();
493 anErr->detailError(detail);
494 }
495 noteNegotiationDone(anErr);
496 bail(anErr);
497 }
498
499 Security::EncryptorAnswer &
500 Security::PeerConnector::answer()
501 {
502 assert(callback);
503 const auto dialer = dynamic_cast<CbDialer*>(callback->getDialer());
504 assert(dialer);
505 return dialer->answer();
506 }
507
508 void
509 Security::PeerConnector::bail(ErrorState *error)
510 {
511 Must(error); // or the recipient will not know there was a problem
512 answer().error = error;
513
514 if (const auto failingConnection = serverConn) {
515 countFailingConnection();
516 disconnect();
517 failingConnection->close();
518 }
519
520 callBack();
521 }
522
523 void
524 Security::PeerConnector::sendSuccess()
525 {
526 assert(Comm::IsConnOpen(serverConn));
527 answer().conn = serverConn;
528 disconnect();
529 callBack();
530 }
531
532 void
533 Security::PeerConnector::countFailingConnection()
534 {
535 assert(serverConn);
536 if (const auto p = serverConn->getPeer())
537 peerConnectFailed(p);
538 // TODO: Calling PconnPool::noteUses() should not be our responsibility.
539 if (noteFwdPconnUse && serverConn->isOpen())
540 fwdPconnPool->noteUses(fd_table[serverConn->fd].pconn.uses);
541 }
542
543 void
544 Security::PeerConnector::disconnect()
545 {
546 const auto stillOpen = Comm::IsConnOpen(serverConn);
547
548 if (closeHandler) {
549 if (stillOpen)
550 comm_remove_close_handler(serverConn->fd, closeHandler);
551 closeHandler = nullptr;
552 }
553
554 if (stillOpen)
555 commUnsetConnTimeout(serverConn);
556
557 serverConn = nullptr;
558 }
559
560 void
561 Security::PeerConnector::callBack()
562 {
563 debugs(83, 5, "TLS setup ended for " << answer().conn);
564
565 AsyncCall::Pointer cb = callback;
566 // Do this now so that if we throw below, swanSong() assert that we _tried_
567 // to call back holds.
568 callback = nullptr; // this should make done() true
569 ScheduleCallHere(cb);
570 }
571
572 void
573 Security::PeerConnector::swanSong()
574 {
575 // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
576 AsyncJob::swanSong();
577
578 if (callback) {
579 // job-ending emergencies like handleStopRequest() or callException()
580 const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
581 bail(anErr);
582 assert(!callback);
583 return;
584 }
585 }
586
587 const char *
588 Security::PeerConnector::status() const
589 {
590 static MemBuf buf;
591 buf.reset();
592
593 // TODO: redesign AsyncJob::status() API to avoid this
594 // id and stop reason reporting duplication.
595 buf.append(" [", 2);
596 if (stopReason != nullptr) {
597 buf.append("Stopped, reason:", 16);
598 buf.appendf("%s",stopReason);
599 }
600 if (Comm::IsConnOpen(serverConn))
601 buf.appendf(" FD %d", serverConn->fd);
602 buf.appendf(" %s%u]", id.prefix(), id.value);
603 buf.terminate();
604
605 return buf.content();
606 }
607
608 #if USE_OPENSSL
609 /// CallDialer to allow use Downloader objects within PeerConnector class.
610 class PeerConnectorCertDownloaderDialer: public Downloader::CbDialer
611 {
612 public:
613 typedef void (Security::PeerConnector::*Method)(SBuf &object, int status);
614
615 PeerConnectorCertDownloaderDialer(Method method, Security::PeerConnector *pc):
616 method_(method),
617 peerConnector_(pc) {}
618
619 /* CallDialer API */
620 virtual bool canDial(AsyncCall &) { return peerConnector_.valid(); }
621 virtual void dial(AsyncCall &) { ((&(*peerConnector_))->*method_)(object, status); }
622 Method method_; ///< The Security::PeerConnector method to dial
623 CbcPointer<Security::PeerConnector> peerConnector_; ///< The Security::PeerConnector object
624 };
625
626 /// the number of concurrent PeerConnector jobs waiting for us
627 unsigned int
628 Security::PeerConnector::certDownloadNestingLevel() const
629 {
630 if (request) {
631 // Nesting level increases when a PeerConnector (at level L) creates a
632 // Downloader (which is assigned level L+1). If we were initiated by
633 // such a Downloader, then their nesting level is our nesting level.
634 if (const auto previousDownloader = request->downloader.get())
635 return previousDownloader->nestedLevel();
636 }
637 return 0; // no other PeerConnector job waits for us
638 }
639
640 void
641 Security::PeerConnector::startCertDownloading(SBuf &url)
642 {
643 AsyncCall::Pointer certCallback = asyncCall(81, 4,
644 "Security::PeerConnector::certDownloadingDone",
645 PeerConnectorCertDownloaderDialer(&Security::PeerConnector::certDownloadingDone, this));
646
647 const auto dl = new Downloader(url, certCallback,
648 MasterXaction::MakePortless<XactionInitiator::initCertFetcher>(),
649 certDownloadNestingLevel() + 1);
650 certDownloadWait.start(dl, certCallback);
651 }
652
653 void
654 Security::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus)
655 {
656 certDownloadWait.finish();
657
658 ++certsDownloads;
659 debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length());
660
661 Must(Comm::IsConnOpen(serverConnection()));
662 const auto &sconn = *fd_table[serverConnection()->fd].ssl;
663
664 // Parse Certificate. Assume that it is in DER format.
665 // According to RFC 4325:
666 // The server must provide a DER encoded certificate or a collection
667 // collection of certificates in a "certs-only" CMS message.
668 // The applications MUST accept DER encoded certificates and SHOULD
669 // be able to accept collection of certificates.
670 // TODO: support collection of certificates
671 const unsigned char *raw = (const unsigned char*)obj.rawContent();
672 if (X509 *cert = d2i_X509(nullptr, &raw, obj.length())) {
673 debugs(81, 5, "Retrieved certificate: " << *cert);
674
675 if (!downloadedCerts)
676 downloadedCerts.reset(sk_X509_new_null());
677 sk_X509_push(downloadedCerts.get(), cert);
678
679 ContextPointer ctx(getTlsContext());
680 const auto certsList = SSL_get_peer_cert_chain(&sconn);
681 if (!Ssl::findIssuerCertificate(cert, certsList, ctx)) {
682 if (const auto issuerUri = Ssl::findIssuerUri(cert)) {
683 debugs(81, 5, "certificate " << *cert <<
684 " points to its missing issuer certificate at " << issuerUri);
685 urlsOfMissingCerts.push(SBuf(issuerUri));
686 } else {
687 debugs(81, 3, "found a certificate with no IAI, " <<
688 "signed by a missing issuer certificate: " << *cert);
689 // We could short-circuit here, proceeding to chain validation
690 // that is likely to fail. Instead, we keep going because we
691 // hope that if we find at least one certificate to fetch, it
692 // will complete the chain (that contained extra certificates).
693 }
694 }
695 }
696
697 // Check if there are URIs to download from and if yes start downloading
698 // the first in queue.
699 if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) {
700 startCertDownloading(urlsOfMissingCerts.front());
701 urlsOfMissingCerts.pop();
702 return;
703 }
704
705 resumeNegotiation();
706 }
707
708 void
709 Security::PeerConnector::handleMissingCertificates(const Security::IoResult &ioResult)
710 {
711 Must(Comm::IsConnOpen(serverConnection()));
712 auto &sconn = *fd_table[serverConnection()->fd].ssl;
713
714 // We download the missing certificate(s) once. We would prefer to clear
715 // this right after the first validation, but that ideal place is _inside_
716 // OpenSSL if validation is triggered by SSL_connect(). That function and
717 // our OpenSSL verify_callback function (\ref OpenSSL_vcb_disambiguation)
718 // may be called multiple times, so we cannot reset there.
719 auto &callerHandlesMissingCertificates = Ssl::VerifyCallbackParameters::At(sconn).callerHandlesMissingCertificates;
720 Must(callerHandlesMissingCertificates);
721 callerHandlesMissingCertificates = false;
722
723 suspendNegotiation(ioResult);
724
725 if (!computeMissingCertificateUrls(sconn))
726 return resumeNegotiation();
727
728 assert(!urlsOfMissingCerts.empty());
729 startCertDownloading(urlsOfMissingCerts.front());
730 urlsOfMissingCerts.pop();
731 }
732
733 /// finds URLs of (some) missing intermediate certificates or returns false
734 bool
735 Security::PeerConnector::computeMissingCertificateUrls(const Connection &sconn)
736 {
737 const auto certs = SSL_get_peer_cert_chain(&sconn);
738 if (!certs) {
739 debugs(83, 3, "nothing to bootstrap the fetch with");
740 return false;
741 }
742 debugs(83, 5, "server certificates: " << sk_X509_num(certs));
743
744 const auto ctx = getTlsContext();
745 if (!Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, *certs, ctx))
746 return false; // missingChainCertificatesUrls() reports the exact reason
747
748 debugs(83, 5, "URLs: " << urlsOfMissingCerts.size());
749 assert(!urlsOfMissingCerts.empty());
750 return true;
751 }
752
753 void
754 Security::PeerConnector::suspendNegotiation(const Security::IoResult &ioResult)
755 {
756 debugs(83, 5, "after " << ioResult);
757 Must(!isSuspended());
758 suspendedError_ = new Security::IoResult(ioResult);
759 Must(isSuspended());
760 // negotiations resume with a resumeNegotiation() call
761 }
762
763 void
764 Security::PeerConnector::resumeNegotiation()
765 {
766 Must(isSuspended());
767
768 auto lastError = suspendedError_; // may be reset below
769 suspendedError_ = nullptr;
770
771 auto &sconn = *fd_table[serverConnection()->fd].ssl;
772 if (!Ssl::VerifyConnCertificates(sconn, downloadedCerts)) {
773 // simulate an earlier SSL_connect() failure with a new error
774 // TODO: When we can use Security::ErrorDetail, we should resume with a
775 // detailed _validation_ error, not just a generic SSL_ERROR_SSL!
776 const ErrorDetail::Pointer errorDetail = new ErrorDetail(SQUID_TLS_ERR_CONNECT, SSL_ERROR_SSL, 0);
777 lastError = new Security::IoResult(errorDetail);
778 }
779
780 handleNegotiationResult(*lastError);
781 }
782
783 #endif //USE_OPENSSL
784