2 * Copyright (C) 1996-2022 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.
9 /* DEBUG: section 83 TLS Server/Peer negotiation */
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"
20 #include "http/Stream.h"
21 #include "HttpRequest.h"
22 #include "neighbors.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"
31 #include "ssl/cert_validate_message.h"
32 #include "ssl/Config.h"
33 #include "ssl/helper.h"
36 CBDATA_NAMESPACED_CLASS_INIT(Security
, PeerConnector
);
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
),
44 negotiationTimeout(timeout
),
45 startTime(squid_curtime
),
46 useCertValidator_(true),
49 debugs(83, 5, serverConn
);
51 // if this throws, the caller's cb dialer is not our CbDialer
52 Must(dynamic_cast<CbDialer
*>(callback
->getDialer()));
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
);
62 Security::PeerConnector::~PeerConnector() = default;
64 bool Security::PeerConnector::doneAll() const
66 return (!callback
|| callback
->canceled()) && AsyncJob::doneAll();
69 /// Preps connection and SSL state. Calls negotiate().
71 Security::PeerConnector::start()
74 debugs(83, 5, "this=" << (void*)this);
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
));
84 Security::SessionPointer tmp
;
88 mustStop("Security::PeerConnector TLS socket initialize failed");
92 Security::PeerConnector::fillChecklist(ACLFilledChecklist
&checklist
) const
96 checklist
.syncAle(request
.getRaw(), nullptr);
97 // checklist.fd(fd); XXX: need client FD here
100 if (!checklist
.serverCert
) {
101 if (const auto session
= fd_table
[serverConnection()->fd
].ssl
.get())
102 checklist
.serverCert
.resetWithoutLocking(SSL_get_peer_certificate(session
));
105 // checklist.serverCert is not maintained in other builds
110 Security::PeerConnector::commCloseHandler(const CommCloseCbParams
¶ms
)
112 debugs(83, 5, "FD " << params
.fd
<< ", Security::PeerConnector=" << params
.data
);
114 closeHandler
= nullptr;
116 countFailingConnection();
117 serverConn
->noteClosure();
118 serverConn
= nullptr;
121 const auto err
= new ErrorState(ERR_SECURE_CONNECT_FAIL
, Http::scServiceUnavailable
, request
.getRaw(), al
);
122 static const auto d
= MakeNamedErrorDetail("TLS_CONNECT_CLOSE");
128 Security::PeerConnector::commTimeoutHandler(const CommTimeoutCbParams
&)
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");
138 Security::PeerConnector::initialize(Security::SessionPointer
&serverSession
)
140 Must(Comm::IsConnOpen(serverConnection()));
142 Security::ContextPointer
ctx(getTlsContext());
143 debugs(83, 5, serverConnection() << ", ctx=" << (void*)ctx
.get());
145 if (!ctx
|| !Security::CreateClientSession(ctx
, serverConnection(), "server https start")) {
146 const auto xerrno
= errno
;
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
);
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());
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
);
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
;
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
;
192 Security::PeerConnector::recordNegotiationDetails()
194 Must(Comm::IsConnOpen(serverConnection()));
196 const int fd
= serverConnection()->fd
;
197 Security::SessionPointer
session(fd_table
[fd
].ssl
);
199 // retrieve TLS server negotiated information if any
200 serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session
);
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
);
212 Security::PeerConnector::negotiate()
214 Must(Comm::IsConnOpen(serverConnection()));
216 const int fd
= serverConnection()->fd
;
217 if (fd_table
[fd
].closing())
220 const auto result
= Security::Connect(*serverConnection());
223 auto &sconn
= *fd_table
[fd
].ssl
;
225 // log ASAP, even if the handshake has not completed (or failed)
226 keyLogger
.checkpoint(sconn
, *this);
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()
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
);
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
254 handleNegotiationResult(result
);
258 Security::PeerConnector::handleNegotiationResult(const Security::IoResult
&result
)
260 switch (result
.category
) {
261 case Security::IoResult::ioSuccess
:
262 recordNegotiationDetails();
263 if (sslFinalized() && callback
)
265 return; // we may be gone by now
267 case Security::IoResult::ioWantRead
:
271 case Security::IoResult::ioWantWrite
:
275 case Security::IoResult::ioError
:
276 break; // fall through to error handling
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
);
288 Security::PeerConnector::sslFinalized()
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
);
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
;
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
);
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
);
320 noteNegotiationDone(anErr
);
327 noteNegotiationDone(nullptr);
333 Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointer validationResponse
)
335 Must(validationResponse
!= nullptr);
336 Must(Comm::IsConnOpen(serverConnection()));
338 ErrorDetail::Pointer errDetails
;
339 bool validatorFailed
= false;
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
));
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
);
354 } else if (validationResponse
->resultCode
!= ::Helper::Okay
)
355 validatorFailed
= true;
357 if (!errDetails
&& !validatorFailed
) {
358 noteNegotiationDone(nullptr);
364 ErrorState
*anErr
= nullptr;
365 if (validatorFailed
) {
366 anErr
= new ErrorState(ERR_GATEWAY_FAILURE
, Http::scInternalServerError
, request
.getRaw(), al
);
368 anErr
= new ErrorState(ERR_SECURE_CONNECT_FAIL
, Http::scServiceUnavailable
, request
.getRaw(), al
);
369 anErr
->detailError(errDetails
);
370 /*anErr->xerrno= Should preserved*/
373 noteNegotiationDone(anErr
);
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
)
386 Must(Comm::IsConnOpen(serverConnection()));
388 ACLFilledChecklist
*check
= nullptr;
389 Security::SessionPointer
session(fd_table
[serverConnection()->fd
].ssl
);
391 if (acl_access
*acl
= ::Config
.ssl_client
.cert_error
) {
392 check
= new ACLFilledChecklist(acl
, request
.getRaw(), dash_str
);
393 fillChecklist(*check
);
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
);
401 assert(i
->error_no
!= SSL_ERROR_NONE
);
404 bool allowed
= false;
406 check
->sslErrors
= new Security::CertErrors(Security::CertError(i
->error_no
, i
->cert
, i
->error_depth
));
407 if (check
->fastCheck().allowed())
410 // else the Config.ssl_client.cert_error access list is not defined
411 // and the first error will cause the error page
414 debugs(83, 3, "bypassing SSL error " << i
->error_no
<< " in " << "buffer");
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
);
423 delete check
->sslErrors
;
424 check
->sslErrors
= nullptr;
429 errs
= new Security::CertErrors(Security::CertError(i
->error_no
, i
->cert
, i
->error_depth
));
431 errs
->push_back_unique(Security::CertError(i
->error_no
, i
->cert
, i
->error_depth
));
440 /// A wrapper for Comm::SetSelect() notifications.
442 Security::PeerConnector::NegotiateSsl(int, void *data
)
444 const auto pc
= static_cast<PeerConnector::Pointer
*>(data
);
446 (*pc
)->negotiateSsl();
450 /// Comm::SetSelect() callback. Direct calls tickle/resume negotiations.
452 Security::PeerConnector::negotiateSsl()
454 // Use job calls to add done() checks and other job logic/protections.
455 CallJobHere(83, 7, this, Security::PeerConnector
, negotiate
);
459 Security::PeerConnector::noteWantRead()
461 debugs(83, 5, serverConnection());
463 Must(Comm::IsConnOpen(serverConnection()));
464 const int fd
= serverConnection()->fd
;
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
);
473 Comm::SetSelect(fd
, COMM_SELECT_READ
, &NegotiateSsl
, new Pointer(this), 0);
477 Security::PeerConnector::noteWantWrite()
479 debugs(83, 5, serverConnection());
480 Must(Comm::IsConnOpen(serverConnection()));
482 const int fd
= serverConnection()->fd
;
483 Comm::SetSelect(fd
, COMM_SELECT_WRITE
, &NegotiateSsl
, new Pointer(this), 0);
488 Security::PeerConnector::noteNegotiationError(const Security::ErrorDetailPointer
&detail
)
490 const auto anErr
= ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL
, request
, al
);
492 anErr
->xerrno
= detail
->sysError();
493 anErr
->detailError(detail
);
495 noteNegotiationDone(anErr
);
499 Security::EncryptorAnswer
&
500 Security::PeerConnector::answer()
503 const auto dialer
= dynamic_cast<CbDialer
*>(callback
->getDialer());
505 return dialer
->answer();
509 Security::PeerConnector::bail(ErrorState
*error
)
511 Must(error
); // or the recipient will not know there was a problem
512 answer().error
= error
;
514 if (const auto failingConnection
= serverConn
) {
515 countFailingConnection();
517 failingConnection
->close();
524 Security::PeerConnector::sendSuccess()
526 assert(Comm::IsConnOpen(serverConn
));
527 answer().conn
= serverConn
;
533 Security::PeerConnector::countFailingConnection()
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
);
544 Security::PeerConnector::disconnect()
546 const auto stillOpen
= Comm::IsConnOpen(serverConn
);
550 comm_remove_close_handler(serverConn
->fd
, closeHandler
);
551 closeHandler
= nullptr;
555 commUnsetConnTimeout(serverConn
);
557 serverConn
= nullptr;
561 Security::PeerConnector::callBack()
563 debugs(83, 5, "TLS setup ended for " << answer().conn
);
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
);
573 Security::PeerConnector::swanSong()
575 // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
576 AsyncJob::swanSong();
579 // job-ending emergencies like handleStopRequest() or callException()
580 const auto anErr
= new ErrorState(ERR_GATEWAY_FAILURE
, Http::scInternalServerError
, request
.getRaw(), al
);
588 Security::PeerConnector::status() const
593 // TODO: redesign AsyncJob::status() API to avoid this
594 // id and stop reason reporting duplication.
596 if (stopReason
!= nullptr) {
597 buf
.append("Stopped, reason:", 16);
598 buf
.appendf("%s",stopReason
);
600 if (Comm::IsConnOpen(serverConn
))
601 buf
.appendf(" FD %d", serverConn
->fd
);
602 buf
.appendf(" %s%u]", id
.prefix(), id
.value
);
605 return buf
.content();
609 /// CallDialer to allow use Downloader objects within PeerConnector class.
610 class PeerConnectorCertDownloaderDialer
: public Downloader::CbDialer
613 typedef void (Security::PeerConnector::*Method
)(SBuf
&object
, int status
);
615 PeerConnectorCertDownloaderDialer(Method method
, Security::PeerConnector
*pc
):
617 peerConnector_(pc
) {}
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
626 /// the number of concurrent PeerConnector jobs waiting for us
628 Security::PeerConnector::certDownloadNestingLevel() const
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();
637 return 0; // no other PeerConnector job waits for us
641 Security::PeerConnector::startCertDownloading(SBuf
&url
)
643 AsyncCall::Pointer certCallback
= asyncCall(81, 4,
644 "Security::PeerConnector::certDownloadingDone",
645 PeerConnectorCertDownloaderDialer(&Security::PeerConnector::certDownloadingDone
, this));
647 const auto dl
= new Downloader(url
, certCallback
,
648 MasterXaction::MakePortless
<XactionInitiator::initCertFetcher
>(),
649 certDownloadNestingLevel() + 1);
650 certDownloadWait
.start(dl
, certCallback
);
654 Security::PeerConnector::certDownloadingDone(SBuf
&obj
, int downloadStatus
)
656 certDownloadWait
.finish();
659 debugs(81, 5, "Certificate downloading status: " << downloadStatus
<< " certificate size: " << obj
.length());
661 Must(Comm::IsConnOpen(serverConnection()));
662 const auto &sconn
= *fd_table
[serverConnection()->fd
].ssl
;
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
);
675 if (!downloadedCerts
)
676 downloadedCerts
.reset(sk_X509_new_null());
677 sk_X509_push(downloadedCerts
.get(), cert
);
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
));
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).
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();
709 Security::PeerConnector::handleMissingCertificates(const Security::IoResult
&ioResult
)
711 Must(Comm::IsConnOpen(serverConnection()));
712 auto &sconn
= *fd_table
[serverConnection()->fd
].ssl
;
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;
723 suspendNegotiation(ioResult
);
725 if (!computeMissingCertificateUrls(sconn
))
726 return resumeNegotiation();
728 assert(!urlsOfMissingCerts
.empty());
729 startCertDownloading(urlsOfMissingCerts
.front());
730 urlsOfMissingCerts
.pop();
733 /// finds URLs of (some) missing intermediate certificates or returns false
735 Security::PeerConnector::computeMissingCertificateUrls(const Connection
&sconn
)
737 const auto certs
= SSL_get_peer_cert_chain(&sconn
);
739 debugs(83, 3, "nothing to bootstrap the fetch with");
742 debugs(83, 5, "server certificates: " << sk_X509_num(certs
));
744 const auto ctx
= getTlsContext();
745 if (!Ssl::missingChainCertificatesUrls(urlsOfMissingCerts
, *certs
, ctx
))
746 return false; // missingChainCertificatesUrls() reports the exact reason
748 debugs(83, 5, "URLs: " << urlsOfMissingCerts
.size());
749 assert(!urlsOfMissingCerts
.empty());
754 Security::PeerConnector::suspendNegotiation(const Security::IoResult
&ioResult
)
756 debugs(83, 5, "after " << ioResult
);
757 Must(!isSuspended());
758 suspendedError_
= new Security::IoResult(ioResult
);
760 // negotiations resume with a resumeNegotiation() call
764 Security::PeerConnector::resumeNegotiation()
768 auto lastError
= suspendedError_
; // may be reset below
769 suspendedError_
= nullptr;
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
);
780 handleNegotiationResult(*lastError
);