]>
Commit | Line | Data |
---|---|---|
a23223bf | 1 | /* |
4ac4a490 | 2 | * Copyright (C) 1996-2017 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" | |
a23223bf | 13 | #include "comm/Loops.h" |
55369ae6 | 14 | #include "Downloader.h" |
a23223bf CT |
15 | #include "errorpage.h" |
16 | #include "fde.h" | |
54fb1cbf | 17 | #include "http/Stream.h" |
a23223bf | 18 | #include "HttpRequest.h" |
36698640 | 19 | #include "security/NegotiationHistory.h" |
a72b6e88 | 20 | #include "security/PeerConnector.h" |
7f4e9b73 | 21 | #include "SquidConfig.h" |
a72b6e88 | 22 | #if USE_OPENSSL |
31855516 | 23 | #include "ssl/bio.h" |
a23223bf CT |
24 | #include "ssl/cert_validate_message.h" |
25 | #include "ssl/Config.h" | |
a23223bf | 26 | #include "ssl/helper.h" |
a72b6e88 | 27 | #endif |
a23223bf | 28 | |
a72b6e88 | 29 | CBDATA_NAMESPACED_CLASS_INIT(Security, PeerConnector); |
a23223bf | 30 | |
a72b6e88 AJ |
31 | Security::PeerConnector::PeerConnector(const Comm::ConnectionPointer &aServerConn, AsyncCall::Pointer &aCallback, const AccessLogEntryPointer &alp, const time_t timeout) : |
32 | AsyncJob("Security::PeerConnector"), | |
f53969cc | 33 | serverConn(aServerConn), |
d4ddb3e6 | 34 | al(alp), |
f53969cc SM |
35 | callback(aCallback), |
36 | negotiationTimeout(timeout), | |
37 | startTime(squid_curtime), | |
54fb1cbf | 38 | useCertValidator_(true), |
4e526b93 | 39 | certsDownloads(0) |
a23223bf | 40 | { |
a72b6e88 | 41 | debugs(83, 5, "Security::PeerConnector constructed, this=" << (void*)this); |
a23223bf CT |
42 | // if this throws, the caller's cb dialer is not our CbDialer |
43 | Must(dynamic_cast<CbDialer*>(callback->getDialer())); | |
44 | } | |
45 | ||
a72b6e88 | 46 | Security::PeerConnector::~PeerConnector() |
a23223bf | 47 | { |
a72b6e88 | 48 | debugs(83, 5, "Security::PeerConnector destructed, this=" << (void*)this); |
a23223bf CT |
49 | } |
50 | ||
a72b6e88 | 51 | bool Security::PeerConnector::doneAll() const |
a23223bf CT |
52 | { |
53 | return (!callback || callback->canceled()) && AsyncJob::doneAll(); | |
54 | } | |
55 | ||
56 | /// Preps connection and SSL state. Calls negotiate(). | |
57 | void | |
a72b6e88 | 58 | Security::PeerConnector::start() |
a23223bf CT |
59 | { |
60 | AsyncJob::start(); | |
61 | ||
eba8d9bb | 62 | Security::SessionPointer tmp; |
0166128b AJ |
63 | if (prepareSocket() && initialize(tmp)) |
64 | negotiate(); | |
ae3ac744 AJ |
65 | else |
66 | mustStop("Security::PeerConnector TLS socket initialize failed"); | |
a23223bf CT |
67 | } |
68 | ||
69 | void | |
a72b6e88 | 70 | Security::PeerConnector::commCloseHandler(const CommCloseCbParams ¶ms) |
a23223bf | 71 | { |
a72b6e88 AJ |
72 | debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data); |
73 | connectionClosed("Security::PeerConnector::commCloseHandler"); | |
a23223bf CT |
74 | } |
75 | ||
76 | void | |
a72b6e88 | 77 | Security::PeerConnector::connectionClosed(const char *reason) |
a23223bf CT |
78 | { |
79 | mustStop(reason); | |
80 | callback = NULL; | |
81 | } | |
82 | ||
83 | bool | |
a72b6e88 | 84 | Security::PeerConnector::prepareSocket() |
a23223bf CT |
85 | { |
86 | const int fd = serverConnection()->fd; | |
87 | if (!Comm::IsConnOpen(serverConn) || fd_table[serverConn->fd].closing()) { | |
a72b6e88 | 88 | connectionClosed("Security::PeerConnector::prepareSocket"); |
a23223bf CT |
89 | return false; |
90 | } | |
91 | ||
92 | // watch for external connection closures | |
a72b6e88 AJ |
93 | typedef CommCbMemFunT<Security::PeerConnector, CommCloseCbParams> Dialer; |
94 | closeHandler = JobCallback(9, 5, Dialer, this, Security::PeerConnector::commCloseHandler); | |
a23223bf CT |
95 | comm_add_close_handler(fd, closeHandler); |
96 | return true; | |
97 | } | |
98 | ||
eba8d9bb | 99 | bool |
0166128b | 100 | Security::PeerConnector::initialize(Security::SessionPointer &serverSession) |
a23223bf | 101 | { |
a72b6e88 | 102 | #if USE_OPENSSL |
b23f5f9c AJ |
103 | Security::ContextPointer ctx(getTlsContext()); |
104 | assert(ctx); | |
a23223bf | 105 | |
b23f5f9c | 106 | if (!Ssl::CreateClient(ctx, serverConnection(), "server https start")) { |
ea574635 AJ |
107 | const auto xerrno = errno; |
108 | const auto ssl_error = ERR_get_error(); | |
a23223bf | 109 | ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw()); |
ea574635 AJ |
110 | anErr->xerrno = xerrno; |
111 | debugs(83, DBG_IMPORTANT, "Error allocating TLS handle: " << Security::ErrorString(ssl_error)); | |
1b091aec CT |
112 | noteNegotiationDone(anErr); |
113 | bail(anErr); | |
eba8d9bb | 114 | return false; |
a23223bf CT |
115 | } |
116 | ||
157c5ace | 117 | // A TLS/SSL session has now been created for the connection and stored in fd_table |
eba8d9bb | 118 | serverSession = fd_table[serverConnection()->fd].ssl; |
157c5ace | 119 | |
a23223bf CT |
120 | // If CertValidation Helper used do not lookup checklist for errors, |
121 | // but keep a list of errors to send it to CertValidator | |
122 | if (!Ssl::TheConfig.ssl_crt_validator) { | |
123 | // Create the ACL check list now, while we have access to more info. | |
124 | // The list is used in ssl_verify_cb() and is freed in ssl_free(). | |
125 | if (acl_access *acl = ::Config.ssl_client.cert_error) { | |
126 | ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); | |
d4ddb3e6 | 127 | check->al = al; |
a23223bf | 128 | // check->fd(fd); XXX: need client FD here |
eba8d9bb | 129 | SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check); |
a23223bf CT |
130 | } |
131 | } | |
a72b6e88 | 132 | |
eba8d9bb | 133 | return true; |
a72b6e88 AJ |
134 | #else |
135 | return false; | |
136 | #endif | |
a23223bf CT |
137 | } |
138 | ||
8aec3e1b | 139 | void |
a72b6e88 | 140 | Security::PeerConnector::setReadTimeout() |
8aec3e1b CT |
141 | { |
142 | int timeToRead; | |
143 | if (negotiationTimeout) { | |
144 | const int timeUsed = squid_curtime - startTime; | |
145 | const int timeLeft = max(0, static_cast<int>(negotiationTimeout - timeUsed)); | |
146 | timeToRead = min(static_cast<int>(::Config.Timeout.read), timeLeft); | |
147 | } else | |
148 | timeToRead = ::Config.Timeout.read; | |
149 | AsyncCall::Pointer nil; | |
150 | commSetConnTimeout(serverConnection(), timeToRead, nil); | |
151 | } | |
152 | ||
36698640 | 153 | void |
a72b6e88 | 154 | Security::PeerConnector::recordNegotiationDetails() |
36698640 CT |
155 | { |
156 | const int fd = serverConnection()->fd; | |
ad23e748 | 157 | Security::SessionPointer session(fd_table[fd].ssl); |
36698640 CT |
158 | |
159 | // retrieve TLS server negotiated information if any | |
ad23e748 AJ |
160 | serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session); |
161 | ||
162 | #if USE_OPENSSL | |
36698640 | 163 | // retrieve TLS parsed extra info |
ad23e748 | 164 | BIO *b = SSL_get_rbio(session.get()); |
2a268a06 | 165 | Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
36698640 CT |
166 | if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails()) |
167 | serverConnection()->tlsNegotiations()->retrieveParsedInfo(details); | |
a72b6e88 | 168 | #endif |
36698640 CT |
169 | } |
170 | ||
a23223bf | 171 | void |
0166128b | 172 | Security::PeerConnector::negotiate() |
a23223bf | 173 | { |
0166128b | 174 | if (!Comm::IsConnOpen(serverConnection())) |
a23223bf CT |
175 | return; |
176 | ||
177 | const int fd = serverConnection()->fd; | |
0166128b AJ |
178 | if (fd_table[fd].closing()) |
179 | return; | |
180 | ||
181 | #if USE_OPENSSL | |
182 | const int result = SSL_connect(fd_table[fd].ssl.get()); | |
a72b6e88 AJ |
183 | #else |
184 | const int result = -1; | |
185 | #endif | |
a23223bf CT |
186 | if (result <= 0) { |
187 | handleNegotiateError(result); | |
188 | return; // we might be gone by now | |
189 | } | |
190 | ||
36698640 CT |
191 | recordNegotiationDetails(); |
192 | ||
c91d4d4e CT |
193 | if (!sslFinalized()) |
194 | return; | |
195 | ||
196 | callBack(); | |
197 | } | |
198 | ||
199 | bool | |
a72b6e88 | 200 | Security::PeerConnector::sslFinalized() |
c91d4d4e | 201 | { |
a72b6e88 | 202 | #if USE_OPENSSL |
1b091aec CT |
203 | if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) { |
204 | const int fd = serverConnection()->fd; | |
ad23e748 | 205 | Security::SessionPointer session(fd_table[fd].ssl); |
a23223bf | 206 | |
a23223bf CT |
207 | Ssl::CertValidationRequest validationRequest; |
208 | // WARNING: Currently we do not use any locking for any of the | |
209 | // members of the Ssl::CertValidationRequest class. In this code the | |
210 | // Ssl::CertValidationRequest object used only to pass data to | |
211 | // Ssl::CertValidationHelper::submit method. | |
ad23e748 | 212 | validationRequest.ssl = session.get(); |
cb171ead CT |
213 | if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server)) |
214 | validationRequest.domainName = dName->c_str(); | |
ad23e748 | 215 | if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors))) |
a23223bf CT |
216 | // validationRequest disappears on return so no need to cbdataReference |
217 | validationRequest.errors = errs; | |
a23223bf CT |
218 | try { |
219 | debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd."); | |
a72b6e88 | 220 | AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr)); |
0e208dad | 221 | Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, call); |
c91d4d4e | 222 | return false; |
a23223bf CT |
223 | } catch (const std::exception &e) { |
224 | debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " << | |
225 | "request for " << validationRequest.domainName << | |
226 | " certificate: " << e.what() << "; will now block to " << | |
227 | "validate that certificate."); | |
228 | // fall through to do blocking in-process generation. | |
229 | ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
1b091aec CT |
230 | |
231 | noteNegotiationDone(anErr); | |
a23223bf | 232 | bail(anErr); |
a23223bf | 233 | serverConn->close(); |
c91d4d4e | 234 | return true; |
a23223bf CT |
235 | } |
236 | } | |
a72b6e88 | 237 | #endif |
1b091aec CT |
238 | |
239 | noteNegotiationDone(NULL); | |
c91d4d4e | 240 | return true; |
a23223bf CT |
241 | } |
242 | ||
a72b6e88 | 243 | #if USE_OPENSSL |
a23223bf | 244 | void |
a72b6e88 | 245 | Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointer validationResponse) |
a23223bf | 246 | { |
0e208dad | 247 | Must(validationResponse != NULL); |
a23223bf | 248 | |
a23223bf CT |
249 | Ssl::ErrorDetail *errDetails = NULL; |
250 | bool validatorFailed = false; | |
251 | if (!Comm::IsConnOpen(serverConnection())) { | |
252 | return; | |
253 | } | |
254 | ||
19ed078f CT |
255 | if (Debug::Enabled(83, 5)) { |
256 | Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl); | |
257 | SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server)); | |
cb171ead | 258 | debugs(83,5, RawPointer("host", server) << " cert validation result: " << validationResponse->resultCode); |
19ed078f | 259 | } |
a23223bf | 260 | |
088f0761 | 261 | if (validationResponse->resultCode == ::Helper::Error) { |
92e3827b | 262 | if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) { |
ad23e748 AJ |
263 | Security::SessionPointer session(fd_table[serverConnection()->fd].ssl); |
264 | Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)); | |
265 | SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs); | |
088f0761 CT |
266 | delete oldErrs; |
267 | } | |
268 | } else if (validationResponse->resultCode != ::Helper::Okay) | |
a23223bf CT |
269 | validatorFailed = true; |
270 | ||
271 | if (!errDetails && !validatorFailed) { | |
1b091aec CT |
272 | noteNegotiationDone(NULL); |
273 | callBack(); | |
a23223bf CT |
274 | return; |
275 | } | |
276 | ||
277 | ErrorState *anErr = NULL; | |
278 | if (validatorFailed) { | |
279 | anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
280 | } else { | |
a23223bf CT |
281 | anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw()); |
282 | anErr->detail = errDetails; | |
283 | /*anErr->xerrno= Should preserved*/ | |
284 | } | |
285 | ||
1b091aec | 286 | noteNegotiationDone(anErr); |
a23223bf | 287 | bail(anErr); |
a23223bf CT |
288 | serverConn->close(); |
289 | return; | |
290 | } | |
a72b6e88 | 291 | #endif |
a23223bf | 292 | |
a72b6e88 | 293 | #if USE_OPENSSL |
a23223bf CT |
294 | /// Checks errors in the cert. validator response against sslproxy_cert_error. |
295 | /// The first honored error, if any, is returned via errDetails parameter. | |
92e3827b AJ |
296 | /// The method returns all seen errors except SSL_ERROR_NONE as Security::CertErrors. |
297 | Security::CertErrors * | |
a72b6e88 | 298 | Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails) |
a23223bf | 299 | { |
a23223bf | 300 | ACLFilledChecklist *check = NULL; |
d4ddb3e6 | 301 | if (acl_access *acl = ::Config.ssl_client.cert_error) { |
a23223bf | 302 | check = new ACLFilledChecklist(acl, request.getRaw(), dash_str); |
d4ddb3e6 CT |
303 | check->al = al; |
304 | } | |
a23223bf | 305 | |
92e3827b | 306 | Security::CertErrors *errs = nullptr; |
ad23e748 | 307 | Security::SessionPointer session(fd_table[serverConnection()->fd].ssl); |
a23223bf CT |
308 | typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI; |
309 | for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) { | |
310 | debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason); | |
311 | ||
312 | assert(i->error_no != SSL_ERROR_NONE); | |
313 | ||
314 | if (!errDetails) { | |
315 | bool allowed = false; | |
316 | if (check) { | |
92e3827b | 317 | check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf CT |
318 | if (check->fastCheck() == ACCESS_ALLOWED) |
319 | allowed = true; | |
320 | } | |
321 | // else the Config.ssl_client.cert_error access list is not defined | |
322 | // and the first error will cause the error page | |
323 | ||
324 | if (allowed) { | |
325 | debugs(83, 3, "bypassing SSL error " << i->error_no << " in " << "buffer"); | |
326 | } else { | |
327 | debugs(83, 5, "confirming SSL error " << i->error_no); | |
328 | X509 *brokenCert = i->cert.get(); | |
ad23e748 | 329 | Security::CertPointer peerCert(SSL_get_peer_certificate(session.get())); |
a23223bf CT |
330 | const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str(); |
331 | errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason); | |
332 | } | |
333 | if (check) { | |
334 | delete check->sslErrors; | |
335 | check->sslErrors = NULL; | |
336 | } | |
337 | } | |
338 | ||
339 | if (!errs) | |
92e3827b | 340 | errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf | 341 | else |
92e3827b | 342 | errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth)); |
a23223bf CT |
343 | } |
344 | if (check) | |
345 | delete check; | |
346 | ||
347 | return errs; | |
348 | } | |
a72b6e88 | 349 | #endif |
a23223bf CT |
350 | |
351 | /// A wrapper for Comm::SetSelect() notifications. | |
352 | void | |
a72b6e88 | 353 | Security::PeerConnector::NegotiateSsl(int, void *data) |
a23223bf | 354 | { |
0166128b | 355 | PeerConnector *pc = static_cast<Security::PeerConnector *>(data); |
a23223bf | 356 | // Use job calls to add done() checks and other job logic/protections. |
0166128b | 357 | CallJobHere(83, 7, pc, Security::PeerConnector, negotiate); |
a23223bf CT |
358 | } |
359 | ||
360 | void | |
a72b6e88 | 361 | Security::PeerConnector::handleNegotiateError(const int ret) |
a23223bf | 362 | { |
a72b6e88 | 363 | #if USE_OPENSSL |
a23223bf CT |
364 | const int fd = serverConnection()->fd; |
365 | unsigned long ssl_lib_error = SSL_ERROR_NONE; | |
ad23e748 AJ |
366 | Security::SessionPointer session(fd_table[fd].ssl); |
367 | const int ssl_error = SSL_get_error(session.get(), ret); | |
a23223bf | 368 | |
e2849af8 | 369 | switch (ssl_error) { |
e2849af8 | 370 | case SSL_ERROR_WANT_READ: |
1b091aec | 371 | noteWantRead(); |
e2849af8 | 372 | return; |
a23223bf | 373 | |
e2849af8 | 374 | case SSL_ERROR_WANT_WRITE: |
1b091aec | 375 | noteWantWrite(); |
e2849af8 | 376 | return; |
a23223bf | 377 | |
e2849af8 A |
378 | case SSL_ERROR_SSL: |
379 | case SSL_ERROR_SYSCALL: | |
380 | ssl_lib_error = ERR_get_error(); | |
1b091aec CT |
381 | // proceed to the general error handling code |
382 | break; | |
383 | default: | |
384 | // no special error handling for all other errors | |
385 | break; | |
386 | } | |
36698640 | 387 | |
d9219c2b | 388 | // Log connection details, if any |
36698640 | 389 | recordNegotiationDetails(); |
0166128b | 390 | noteNegotiationError(ret, ssl_error, ssl_lib_error); |
a72b6e88 | 391 | #endif |
1b091aec | 392 | } |
a23223bf | 393 | |
1b091aec | 394 | void |
a72b6e88 | 395 | Security::PeerConnector::noteWantRead() |
1b091aec | 396 | { |
1b091aec | 397 | const int fd = serverConnection()->fd; |
212e5aee | 398 | #if USE_OPENSSL |
ad23e748 AJ |
399 | Security::SessionPointer session(fd_table[fd].ssl); |
400 | BIO *b = SSL_get_rbio(session.get()); | |
2a268a06 | 401 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
55369ae6 AR |
402 | if (srvBio->holdRead()) { |
403 | if (srvBio->gotHello()) { | |
404 | if (checkForMissingCertificates()) | |
405 | return; // Wait to download certificates before proceed. | |
406 | ||
407 | srvBio->holdRead(false); | |
168d2b30 | 408 | // schedule a negotiateSSl to allow openSSL parse received data |
212e5aee | 409 | Security::PeerConnector::NegotiateSsl(fd, this); |
55369ae6 AR |
410 | return; |
411 | } else if (srvBio->gotHelloFailed()) { | |
412 | srvBio->holdRead(false); | |
413 | debugs(83, DBG_IMPORTANT, "Error parsing SSL Server Hello Message on FD " << fd); | |
168d2b30 | 414 | // schedule a negotiateSSl to allow openSSL parse received data |
212e5aee | 415 | Security::PeerConnector::NegotiateSsl(fd, this); |
55369ae6 AR |
416 | return; |
417 | } | |
418 | } | |
212e5aee | 419 | #endif |
55369ae6 | 420 | setReadTimeout(); |
1b091aec CT |
421 | Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0); |
422 | } | |
7f4e9b73 | 423 | |
1b091aec | 424 | void |
a72b6e88 | 425 | Security::PeerConnector::noteWantWrite() |
1b091aec CT |
426 | { |
427 | const int fd = serverConnection()->fd; | |
428 | Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0); | |
429 | return; | |
430 | } | |
a23223bf | 431 | |
1b091aec | 432 | void |
0166128b | 433 | Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error) |
1b091aec | 434 | { |
a72b6e88 AJ |
435 | #if USE_OPENSSL // not used unless OpenSSL enabled. |
436 | #if defined(EPROTO) | |
1b091aec CT |
437 | int sysErrNo = EPROTO; |
438 | #else | |
439 | int sysErrNo = EACCES; | |
440 | #endif | |
a23223bf | 441 | |
1b091aec CT |
442 | // store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1 |
443 | if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0) | |
444 | sysErrNo = errno; | |
a23223bf | 445 | |
1b091aec CT |
446 | const int fd = serverConnection()->fd; |
447 | debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd << | |
ea574635 | 448 | ": " << Security::ErrorString(ssl_lib_error) << " (" << |
1b091aec | 449 | ssl_error << "/" << ret << "/" << errno << ")"); |
a23223bf | 450 | |
1b091aec CT |
451 | ErrorState *anErr = NULL; |
452 | if (request != NULL) | |
453 | anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw()); | |
454 | else | |
455 | anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL); | |
a23223bf CT |
456 | anErr->xerrno = sysErrNo; |
457 | ||
ad23e748 AJ |
458 | Security::SessionPointer session(fd_table[fd].ssl); |
459 | Ssl::ErrorDetail *errFromFailure = static_cast<Ssl::ErrorDetail *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail)); | |
a23223bf CT |
460 | if (errFromFailure != NULL) { |
461 | // The errFromFailure is attached to the ssl object | |
462 | // and will be released when ssl object destroyed. | |
463 | // Copy errFromFailure to a new Ssl::ErrorDetail object | |
464 | anErr->detail = new Ssl::ErrorDetail(*errFromFailure); | |
465 | } else { | |
466 | // server_cert can be NULL here | |
ad23e748 | 467 | X509 *server_cert = SSL_get_peer_certificate(session.get()); |
a23223bf CT |
468 | anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL); |
469 | X509_free(server_cert); | |
470 | } | |
471 | ||
472 | if (ssl_lib_error != SSL_ERROR_NONE) | |
473 | anErr->detail->setLibError(ssl_lib_error); | |
474 | ||
1b091aec | 475 | noteNegotiationDone(anErr); |
a23223bf | 476 | bail(anErr); |
a72b6e88 | 477 | #endif |
a23223bf CT |
478 | } |
479 | ||
480 | void | |
a72b6e88 | 481 | Security::PeerConnector::bail(ErrorState *error) |
a23223bf CT |
482 | { |
483 | Must(error); // or the recepient will not know there was a problem | |
a23223bf CT |
484 | Must(callback != NULL); |
485 | CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer()); | |
486 | Must(dialer); | |
487 | dialer->answer().error = error; | |
488 | ||
489 | callBack(); | |
490 | // Our job is done. The callabck recepient will probably close the failed | |
491 | // peer connection and try another peer or go direct (if possible). We | |
492 | // can close the connection ourselves (our error notification would reach | |
493 | // the recepient before the fd-closure notification), but we would rather | |
494 | // minimize the number of fd-closure notifications and let the recepient | |
495 | // manage the TCP state of the connection. | |
496 | } | |
497 | ||
498 | void | |
a72b6e88 | 499 | Security::PeerConnector::callBack() |
a23223bf CT |
500 | { |
501 | AsyncCall::Pointer cb = callback; | |
502 | // Do this now so that if we throw below, swanSong() assert that we _tried_ | |
503 | // to call back holds. | |
504 | callback = NULL; // this should make done() true | |
505 | ||
506 | // remove close handler | |
507 | comm_remove_close_handler(serverConnection()->fd, closeHandler); | |
508 | ||
509 | CbDialer *dialer = dynamic_cast<CbDialer*>(cb->getDialer()); | |
510 | Must(dialer); | |
511 | dialer->answer().conn = serverConnection(); | |
512 | ScheduleCallHere(cb); | |
513 | } | |
514 | ||
a23223bf | 515 | void |
a72b6e88 | 516 | Security::PeerConnector::swanSong() |
a23223bf CT |
517 | { |
518 | // XXX: unregister fd-closure monitoring and CommSetSelect interest, if any | |
519 | AsyncJob::swanSong(); | |
566f8310 AR |
520 | if (callback != NULL) { // paranoid: we have left the caller waiting |
521 | debugs(83, DBG_IMPORTANT, "BUG: Unexpected state while connecting to a cache_peer or origin server"); | |
522 | ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw()); | |
523 | bail(anErr); | |
524 | assert(!callback); | |
525 | return; | |
526 | } | |
a23223bf CT |
527 | } |
528 | ||
529 | const char * | |
a72b6e88 | 530 | Security::PeerConnector::status() const |
a23223bf CT |
531 | { |
532 | static MemBuf buf; | |
533 | buf.reset(); | |
534 | ||
535 | // TODO: redesign AsyncJob::status() API to avoid this | |
536 | // id and stop reason reporting duplication. | |
537 | buf.append(" [", 2); | |
538 | if (stopReason != NULL) { | |
07e6d76e AJ |
539 | buf.append("Stopped, reason:", 16); |
540 | buf.appendf("%s",stopReason); | |
a23223bf CT |
541 | } |
542 | if (serverConn != NULL) | |
07e6d76e | 543 | buf.appendf(" FD %d", serverConn->fd); |
f2e41480 | 544 | buf.appendf(" %s%u]", id.prefix(), id.value); |
a23223bf CT |
545 | buf.terminate(); |
546 | ||
547 | return buf.content(); | |
548 | } | |
549 | ||
212e5aee | 550 | #if USE_OPENSSL |
6cae08c9 | 551 | /// CallDialer to allow use Downloader objects within PeerConnector class. |
4cab96c5 | 552 | class PeerConnectorCertDownloaderDialer: public Downloader::CbDialer |
55369ae6 AR |
553 | { |
554 | public: | |
212e5aee | 555 | typedef void (Security::PeerConnector::*Method)(SBuf &object, int status); |
55369ae6 | 556 | |
212e5aee | 557 | PeerConnectorCertDownloaderDialer(Method method, Security::PeerConnector *pc): |
55369ae6 AR |
558 | method_(method), |
559 | peerConnector_(pc) {} | |
560 | ||
561 | /* CallDialer API */ | |
562 | virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); } | |
4cab96c5 | 563 | virtual void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); } |
212e5aee CT |
564 | Method method_; ///< The Security::PeerConnector method to dial |
565 | CbcPointer<Security::PeerConnector> peerConnector_; ///< The Security::PeerConnector object | |
55369ae6 AR |
566 | }; |
567 | ||
568 | void | |
212e5aee | 569 | Security::PeerConnector::startCertDownloading(SBuf &url) |
55369ae6 AR |
570 | { |
571 | AsyncCall::Pointer certCallback = asyncCall(81, 4, | |
3945c91d SM |
572 | "Security::PeerConnector::certDownloadingDone", |
573 | PeerConnectorCertDownloaderDialer(&Security::PeerConnector::certDownloadingDone, this)); | |
55369ae6 | 574 | |
19ed078f | 575 | const Downloader *csd = (request ? dynamic_cast<const Downloader*>(request->downloader.valid()) : nullptr); |
cda7024f | 576 | Downloader *dl = new Downloader(url, certCallback, csd ? csd->nestedLevel() + 1 : 1); |
55369ae6 AR |
577 | AsyncJob::Start(dl); |
578 | } | |
579 | ||
580 | void | |
212e5aee | 581 | Security::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus) |
55369ae6 | 582 | { |
4b5ea8a6 | 583 | ++certsDownloads; |
7b4984f7 | 584 | debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length()); |
55369ae6 | 585 | |
168d2b30 | 586 | // get ServerBio from SSL object |
55369ae6 | 587 | const int fd = serverConnection()->fd; |
ad23e748 AJ |
588 | Security::SessionPointer session(fd_table[fd].ssl); |
589 | BIO *b = SSL_get_rbio(session.get()); | |
2a268a06 | 590 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
55369ae6 | 591 | |
25d0ea14 | 592 | // Parse Certificate. Assume that it is in DER format. |
c31381d0 CT |
593 | // According to RFC 4325: |
594 | // The server must provide a DER encoded certificate or a collection | |
595 | // collection of certificates in a "certs-only" CMS message. | |
596 | // The applications MUST accept DER encoded certificates and SHOULD | |
597 | // be able to accept collection of certificates. | |
598 | // TODO: support collection of certificates | |
55369ae6 AR |
599 | const unsigned char *raw = (const unsigned char*)obj.rawContent(); |
600 | if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) { | |
601 | char buffer[1024]; | |
602 | debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024)); | |
a34d1d2d | 603 | const Security::CertList &certsList = srvBio->serverCertificatesIfAny(); |
55369ae6 AR |
604 | if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList)) { |
605 | urlsOfMissingCerts.push(SBuf(issuerUri)); | |
606 | } | |
ad23e748 | 607 | Ssl::SSL_add_untrusted_cert(session.get(), cert); |
55369ae6 AR |
608 | } |
609 | ||
4b5ea8a6 CT |
610 | // Check if there are URIs to download from and if yes start downloading |
611 | // the first in queue. | |
4e526b93 | 612 | if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) { |
55369ae6 AR |
613 | startCertDownloading(urlsOfMissingCerts.front()); |
614 | urlsOfMissingCerts.pop(); | |
615 | return; | |
616 | } | |
617 | ||
618 | srvBio->holdRead(false); | |
212e5aee | 619 | Security::PeerConnector::NegotiateSsl(serverConnection()->fd, this); |
55369ae6 AR |
620 | } |
621 | ||
622 | bool | |
212e5aee | 623 | Security::PeerConnector::checkForMissingCertificates() |
55369ae6 | 624 | { |
4e526b93 CT |
625 | // Check for nested SSL certificates downloads. For example when the |
626 | // certificate located in an SSL site which requires to download a | |
168d2b30 | 627 | // a missing certificate (... from an SSL site which requires to ...). |
cda7024f | 628 | |
19ed078f | 629 | const Downloader *csd = (request ? request->downloader.get() : nullptr); |
4e526b93 CT |
630 | if (csd && csd->nestedLevel() >= MaxNestedDownloads) |
631 | return false; | |
632 | ||
55369ae6 | 633 | const int fd = serverConnection()->fd; |
ad23e748 AJ |
634 | Security::SessionPointer session(fd_table[fd].ssl); |
635 | BIO *b = SSL_get_rbio(session.get()); | |
2a268a06 | 636 | Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b)); |
a34d1d2d | 637 | const Security::CertList &certs = srvBio->serverCertificatesIfAny(); |
55369ae6 | 638 | |
a34d1d2d CT |
639 | if (certs.size()) { |
640 | debugs(83, 5, "SSL server sent " << certs.size() << " certificates"); | |
55369ae6 AR |
641 | Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs); |
642 | if (urlsOfMissingCerts.size()) { | |
643 | startCertDownloading(urlsOfMissingCerts.front()); | |
644 | urlsOfMissingCerts.pop(); | |
645 | return true; | |
646 | } | |
647 | } | |
648 | ||
649 | return false; | |
650 | } | |
212e5aee | 651 | #endif //USE_OPENSSL |
3945c91d | 652 |