]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ssl/PeekingPeerConnector.cc
fa7d29c9fd7f6f8dd3b8c93fff9a97922c2a4b17
[thirdparty/squid.git] / src / ssl / PeekingPeerConnector.cc
1 /*
2 * Copyright (C) 1996-2025 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 SSL-Bump Server/Peer negotiation */
10
11 #include "squid.h"
12 #include "acl/FilledChecklist.h"
13 #include "client_side.h"
14 #include "errorpage.h"
15 #include "fde.h"
16 #include "http/Stream.h"
17 #include "HttpRequest.h"
18 #include "security/ErrorDetail.h"
19 #include "security/NegotiationHistory.h"
20 #include "SquidConfig.h"
21 #include "ssl/bio.h"
22 #include "ssl/PeekingPeerConnector.h"
23 #include "ssl/ServerBump.h"
24 #include "tunnel.h"
25
26 CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector);
27
28 Ssl::PeekingPeerConnector::PeekingPeerConnector(HttpRequestPointer &aRequest,
29 const Comm::ConnectionPointer &aServerConn,
30 const Comm::ConnectionPointer &aClientConn,
31 const AsyncCallback<Security::EncryptorAnswer> &aCallback,
32 const AccessLogEntryPointer &alp,
33 const time_t timeout):
34 AsyncJob("Ssl::PeekingPeerConnector"),
35 Security::PeerConnector(aServerConn, aCallback, alp, timeout),
36 clientConn(aClientConn),
37 splice(false),
38 serverCertificateHandled(false)
39 {
40 request = aRequest;
41
42 if (const auto csd = request->clientConnectionManager.valid()) {
43 const auto serverBump = csd->serverBump();
44 Must(serverBump);
45 Must(serverBump->at(XactionStep::tlsBump3));
46 }
47 // else the client is gone, and we cannot check the step, but must carry on
48 }
49
50 void
51 Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone(const Acl::Answer aclAnswer, void *data)
52 {
53 Ssl::PeekingPeerConnector *peerConnect = (Ssl::PeekingPeerConnector *) data;
54 // Use job calls to add done() checks and other job logic/protections.
55 CallJobHere1(83, 7, CbcPointer<PeekingPeerConnector>(peerConnect), Ssl::PeekingPeerConnector, checkForPeekAndSpliceDone, aclAnswer);
56 }
57
58 void
59 Ssl::PeekingPeerConnector::checkForPeekAndSpliceDone(const Acl::Answer aclAnswer)
60 {
61 const Ssl::BumpMode finalAction = aclAnswer.allowed() ?
62 static_cast<Ssl::BumpMode>(aclAnswer.kind):
63 checkForPeekAndSpliceGuess();
64 checkForPeekAndSpliceMatched(finalAction);
65 }
66
67 void
68 Ssl::PeekingPeerConnector::checkForPeekAndSplice()
69 {
70 handleServerCertificate();
71
72 auto acl_checklist = ACLFilledChecklist::Make(::Config.accessList.ssl_bump, request.getRaw());
73 acl_checklist->al = al;
74 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpNone));
75 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpPeek));
76 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpStare));
77 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpClientFirst));
78 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpServerFirst));
79 Security::SessionPointer session(fd_table[serverConn->fd].ssl);
80 BIO *b = SSL_get_rbio(session.get());
81 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
82 if (!srvBio->canSplice())
83 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpSplice));
84 if (!srvBio->canBump())
85 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpBump));
86 acl_checklist->syncAle(request.getRaw(), nullptr);
87 ACLFilledChecklist::NonBlockingCheck(std::move(acl_checklist), Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone, this);
88 }
89
90 void
91 Ssl::PeekingPeerConnector::checkForPeekAndSpliceMatched(const Ssl::BumpMode action)
92 {
93 Security::SessionPointer session(fd_table[serverConn->fd].ssl);
94 BIO *b = SSL_get_rbio(session.get());
95 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
96 debugs(83,5, "Will check for peek and splice on FD " << serverConn->fd);
97
98 Ssl::BumpMode finalAction = action;
99 Must(finalAction == Ssl::bumpSplice || finalAction == Ssl::bumpBump || finalAction == Ssl::bumpTerminate);
100 // Record final decision
101 if (request->clientConnectionManager.valid()) {
102 request->clientConnectionManager->sslBumpMode = finalAction;
103 request->clientConnectionManager->serverBump()->act.step3 = finalAction;
104 }
105 al->ssl.bumpMode = finalAction;
106
107 if (finalAction == Ssl::bumpTerminate) {
108 bail(new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scForbidden, request.getRaw(), al));
109 clientConn->close();
110 clientConn = nullptr;
111 } else if (finalAction != Ssl::bumpSplice) {
112 //Allow write, proceed with the connection
113 srvBio->holdWrite(false);
114 srvBio->recordInput(false);
115 debugs(83,5, "Retry the fwdNegotiateSSL on FD " << serverConn->fd);
116 Security::PeerConnector::noteWantWrite();
117 } else {
118 splice = true;
119 // Ssl Negotiation stops here. Last SSL checks for valid certificates
120 // and if done, switch to tunnel mode
121 if (sslFinalized() && callback)
122 callBack();
123 }
124 }
125
126 Ssl::BumpMode
127 Ssl::PeekingPeerConnector::checkForPeekAndSpliceGuess() const
128 {
129 if (const ConnStateData *csd = request->clientConnectionManager.valid()) {
130 const Ssl::BumpMode currentMode = csd->sslBumpMode;
131 if (currentMode == Ssl::bumpStare) {
132 debugs(83,5, "default to bumping after staring");
133 return Ssl::bumpBump;
134 }
135 debugs(83,5, "default to splicing after " << currentMode);
136 } else {
137 debugs(83,3, "default to splicing due to missing info");
138 }
139
140 return Ssl::bumpSplice;
141 }
142
143 Security::FuturePeerContext *
144 Ssl::PeekingPeerConnector::peerContext() const
145 {
146 return ::Config.ssl_client.defaultPeerContext;
147 }
148
149 bool
150 Ssl::PeekingPeerConnector::initialize(Security::SessionPointer &serverSession)
151 {
152 if (!Security::PeerConnector::initialize(serverSession))
153 return false;
154
155 // client connection supplies TLS client details and is also used if we
156 // need to splice or terminate the client and server connections
157 if (!Comm::IsConnOpen(clientConn))
158 return false;
159
160 if (ConnStateData *csd = request->clientConnectionManager.valid()) {
161
162 SBuf *hostName = nullptr;
163
164 //Enable Status_request TLS extension, required to bump some clients
165 SSL_set_tlsext_status_type(serverSession.get(), TLSEXT_STATUSTYPE_ocsp);
166
167 const Security::TlsDetails::Pointer details = csd->tlsParser.details;
168 if (details && !details->serverName.isEmpty())
169 hostName = new SBuf(details->serverName);
170
171 if (!hostName) {
172 // While we are peeking at the certificate, we may not know the server
173 // name that the client will request (after interception or CONNECT)
174 // unless it was the CONNECT request with a user-typed address.
175 const bool isConnectRequest = !csd->port->flags.isIntercepted();
176 if (!request->flags.sslPeek || isConnectRequest)
177 hostName = new SBuf(request->url.host());
178 }
179
180 if (hostName)
181 SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, (void*)hostName);
182
183 if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
184 auto clientSession = fd_table[clientConn->fd].ssl.get();
185 Must(clientSession);
186 BIO *bc = SSL_get_rbio(clientSession);
187 Ssl::ClientBio *cltBio = static_cast<Ssl::ClientBio *>(BIO_get_data(bc));
188 Must(cltBio);
189 if (details && details->tlsVersion.protocol != AnyP::PROTO_NONE)
190 applyTlsDetailsToSSL(serverSession.get(), details, csd->sslBumpMode);
191
192 BIO *b = SSL_get_rbio(serverSession.get());
193 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
194 Must(srvBio);
195 // inherit client features such as TLS version and SNI
196 srvBio->setClientFeatures(details, cltBio->rBufData());
197 srvBio->recordInput(true);
198 srvBio->mode(csd->sslBumpMode);
199 } else {
200 const bool redirected = request->flags.redirected && ::Config.onoff.redir_rewrites_host;
201 const char *sniServer = (!hostName || redirected) ?
202 request->url.host() :
203 hostName->c_str();
204 if (sniServer)
205 setClientSNI(serverSession.get(), sniServer);
206 }
207
208 if (Ssl::ServerBump *serverBump = csd->serverBump()) {
209 serverBump->attachServerSession(serverSession);
210 // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
211 if (X509 *peeked_cert = serverBump->serverCert.get()) {
212 X509_up_ref(peeked_cert);
213 SSL_set_ex_data(serverSession.get(), ssl_ex_index_ssl_peeked_cert, peeked_cert);
214 }
215 }
216 }
217
218 return true;
219 }
220
221 void
222 Ssl::PeekingPeerConnector::noteNegotiationDone(ErrorState *error)
223 {
224 // Check the list error with
225 if (!request->clientConnectionManager.valid() || !fd_table[serverConnection()->fd].ssl)
226 return;
227
228 // remember the server certificate from the ErrorDetail object
229 if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
230 if (!serverBump->serverCert.get()) {
231 // remember the server certificate from the ErrorDetail object
232 const auto errDetail = dynamic_cast<Security::ErrorDetail *>(error ? error->detail.getRaw() : nullptr);
233 if (errDetail && errDetail->peerCert())
234 serverBump->serverCert.resetAndLock(errDetail->peerCert());
235 else {
236 handleServerCertificate();
237 }
238 }
239
240 if (error) {
241 // For intercepted connections, set the host name to the server
242 // certificate CN. Otherwise, we just hope that CONNECT is using
243 // a user-entered address (a host name or a user-entered IP).
244 const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted();
245 if (request->flags.sslPeek && !isConnectRequest) {
246 if (X509 *srvX509 = serverBump->serverCert.get()) {
247 if (const char *name = Ssl::CommonHostName(srvX509)) {
248 request->url.host(name);
249 debugs(83, 3, "reset request host: " << name);
250 }
251 }
252 }
253 }
254 }
255
256 if (!error) {
257 serverCertificateVerified();
258 if (splice) {
259 if (!Comm::IsConnOpen(clientConn)) {
260 bail(new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al));
261 throw TextException("from-client connection gone", Here());
262 }
263 startTunneling();
264 }
265 }
266 }
267
268 void
269 Ssl::PeekingPeerConnector::startTunneling()
270 {
271 // switchToTunnel() drains any already buffered from-server data (rBufData)
272 fd_table[serverConn->fd].useDefaultIo();
273 // tunnelStartShoveling() drains any buffered from-client data (inBuf)
274 fd_table[clientConn->fd].useDefaultIo();
275
276 // TODO: Encapsulate this frequently repeated logic into a method.
277 const auto session = fd_table[serverConn->fd].ssl;
278 auto b = SSL_get_rbio(session.get());
279 auto srvBio = static_cast<Ssl::ServerBio*>(BIO_get_data(b));
280
281 debugs(83, 5, "will tunnel instead of negotiating TLS");
282 switchToTunnel(request.getRaw(), clientConn, serverConn, srvBio->rBufData());
283 answer().tunneled = true;
284 disconnect();
285 callBack();
286 }
287
288 void
289 Ssl::PeekingPeerConnector::noteWantWrite()
290 {
291 const int fd = serverConnection()->fd;
292 Security::SessionPointer session(fd_table[fd].ssl);
293 BIO *b = SSL_get_rbio(session.get());
294 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
295
296 if ((srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
297 debugs(81, 3, "hold write on SSL connection on FD " << fd);
298 checkForPeekAndSplice();
299 return;
300 }
301
302 Security::PeerConnector::noteWantWrite();
303 }
304
305 void
306 Ssl::PeekingPeerConnector::noteNegotiationError(const Security::ErrorDetailPointer &errorDetail)
307 {
308 const int fd = serverConnection()->fd;
309 Security::SessionPointer session(fd_table[fd].ssl);
310 BIO *b = SSL_get_rbio(session.get());
311 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
312
313 if (srvBio->bumpMode() == Ssl::bumpPeek) {
314 auto bypassValidator = false;
315 if (srvBio->encryptedCertificates()) {
316 // it is pointless to peek at encrypted certificates
317 //
318 // we currently splice all sessions with encrypted certificates
319 // if (const auto spliceEncryptedCertificates = true) {
320 bypassValidator = true;
321 // } // else fall through to find a matching ssl_bump action (with limited info)
322 } else if (srvBio->resumingSession()) {
323 // In peek mode, the ClientHello message is forwarded to the server.
324 // If the server is resuming a previous (spliced) SSL session with
325 // the client, then probably we are here because our local SSL
326 // object does not know anything about the session being resumed.
327 //
328 // we currently splice all resumed sessions
329 // if (const auto spliceResumed = true) {
330 bypassValidator = true;
331 // } // else fall through to find a matching ssl_bump action (with limited info)
332 }
333
334 if (bypassValidator) {
335 bypassCertValidator();
336 checkForPeekAndSpliceMatched(Ssl::bumpSplice);
337 return;
338 }
339 }
340
341 // If we are in peek-and-splice mode and still we did not write to
342 // server yet, try to see if we should splice.
343 // In this case the connection can be saved.
344 // If the checklist decision is do not splice a new error will
345 // occur in the next SSL_connect call, and we will fail again.
346 // Abort on certificate validation errors to avoid splicing and
347 // thus hiding them.
348 // Abort if no certificate found probably because of malformed or
349 // unsupported server Hello message (TODO: make configurable).
350 // TODO: Add/use a positive "successfully validated server cert" signal
351 // instead of relying on the "![presumably_]validation_error && serverCert"
352 // signal combo.
353 if (!SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail) &&
354 (srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
355 Security::CertPointer serverCert(SSL_get_peer_certificate(session.get()));
356 if (serverCert) {
357 debugs(81, 3, "hold TLS write on FD " << fd << " despite " << errorDetail);
358 checkForPeekAndSplice();
359 return;
360 }
361 }
362
363 // else call parent noteNegotiationError to produce an error page
364 Security::PeerConnector::noteNegotiationError(errorDetail);
365 }
366
367 void
368 Ssl::PeekingPeerConnector::handleServerCertificate()
369 {
370 if (serverCertificateHandled)
371 return;
372
373 if (ConnStateData *csd = request->clientConnectionManager.valid()) {
374 const int fd = serverConnection()->fd;
375 Security::SessionPointer session(fd_table[fd].ssl);
376 Security::CertPointer serverCert(SSL_get_peer_certificate(session.get()));
377 if (!serverCert)
378 return;
379
380 serverCertificateHandled = true;
381
382 // remember the server certificate for later use
383 if (Ssl::ServerBump *serverBump = csd->serverBump()) {
384 serverBump->serverCert = std::move(serverCert);
385 }
386 }
387 }
388
389 void
390 Ssl::PeekingPeerConnector::serverCertificateVerified()
391 {
392 if (ConnStateData *csd = request->clientConnectionManager.valid()) {
393 Security::CertPointer serverCert;
394 if(Ssl::ServerBump *serverBump = csd->serverBump())
395 serverCert.resetAndLock(serverBump->serverCert.get());
396 else {
397 const int fd = serverConnection()->fd;
398 Security::SessionPointer session(fd_table[fd].ssl);
399 serverCert.resetWithoutLocking(SSL_get_peer_certificate(session.get()));
400 }
401 if (serverCert) {
402 csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
403 debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
404 " bumped: " << *serverConnection());
405 }
406 }
407 }
408