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