Squid Web Cache master
Loading...
Searching...
No Matches
PeekingPeerConnector.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 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"
20#include "SquidConfig.h"
21#include "src/base/IoManip.h"
22#include "ssl/bio.h"
24#include "ssl/ServerBump.h"
25#include "tunnel.h"
26
27CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector);
28
30 const Comm::ConnectionPointer &aServerConn,
31 const Comm::ConnectionPointer &aClientConn,
33 const AccessLogEntryPointer &alp,
34 const time_t timeout):
35 AsyncJob("Ssl::PeekingPeerConnector"),
36 Security::PeerConnector(aServerConn, aCallback, alp, timeout),
37 clientConn(aClientConn),
38 splice(false),
39 serverCertificateHandled(false)
40{
41 request = aRequest;
42
43 if (const auto csd = request->clientConnectionManager.valid()) {
44 const auto serverBump = csd->serverBump();
45 Must(serverBump);
46 Must(serverBump->at(XactionStep::tlsBump3));
47 }
48 // else the client is gone, and we cannot check the step, but must carry on
49}
50
51void
53{
55 // Use job calls to add done() checks and other job logic/protections.
56 CallJobHere1(83, 7, CbcPointer<PeekingPeerConnector>(peerConnect), Ssl::PeekingPeerConnector, checkForPeekAndSpliceDone, aclAnswer);
57}
58
59void
61{
62 const Ssl::BumpMode finalAction = aclAnswer.allowed() ?
63 static_cast<Ssl::BumpMode>(aclAnswer.kind):
64 checkForPeekAndSpliceGuess();
65 checkForPeekAndSpliceMatched(finalAction);
66}
67
68void
70{
71 handleServerCertificate();
72
73 auto acl_checklist = ACLFilledChecklist::Make(::Config.accessList.ssl_bump, request.getRaw());
74 acl_checklist->al = al;
75 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpNone));
76 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpPeek));
77 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpStare));
78 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpClientFirst));
79 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpServerFirst));
80 Security::SessionPointer session(fd_table[serverConn->fd].ssl);
81 BIO *b = SSL_get_rbio(session.get());
82 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
83 if (!srvBio->canSplice())
84 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpSplice));
85 if (!srvBio->canBump())
86 acl_checklist->banAction(Acl::Answer(ACCESS_ALLOWED, Ssl::bumpBump));
87 acl_checklist->syncAle(request.getRaw(), nullptr);
89}
90
91void
93{
94 Security::SessionPointer session(fd_table[serverConn->fd].ssl);
95 BIO *b = SSL_get_rbio(session.get());
96 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
97 debugs(83,5, "Will check for peek and splice on FD " << serverConn->fd);
98
99 Ssl::BumpMode finalAction = action;
100 Must(finalAction == Ssl::bumpSplice || finalAction == Ssl::bumpBump || finalAction == Ssl::bumpTerminate);
101 // Record final decision
102 if (request->clientConnectionManager.valid()) {
103 request->clientConnectionManager->sslBumpMode = finalAction;
104 request->clientConnectionManager->serverBump()->act.step3 = finalAction;
105 }
106 al->ssl.bumpMode = finalAction;
107
108 if (finalAction == Ssl::bumpTerminate) {
109 bail(new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scForbidden, request.getRaw(), al));
110 clientConn->close();
111 clientConn = nullptr;
112 } else if (finalAction != Ssl::bumpSplice) {
113 //Allow write, proceed with the connection
114 srvBio->holdWrite(false);
115 srvBio->recordInput(false);
116 debugs(83,5, "Retry the fwdNegotiateSSL on FD " << serverConn->fd);
118 } else {
119 splice = true;
120 // Ssl Negotiation stops here. Last SSL checks for valid certificates
121 // and if done, switch to tunnel mode
122 if (sslFinalized() && callback)
123 callBack();
124 }
125}
126
129{
130 if (const ConnStateData *csd = request->clientConnectionManager.valid()) {
131 const Ssl::BumpMode currentMode = csd->sslBumpMode;
132 if (currentMode == Ssl::bumpStare) {
133 debugs(83,5, "default to bumping after staring");
134 return Ssl::bumpBump;
135 }
136 debugs(83,5, "default to splicing after " << currentMode);
137 } else {
138 debugs(83,3, "default to splicing due to missing info");
139 }
140
141 return Ssl::bumpSplice;
142}
143
146{
147 return ::Config.ssl_client.defaultPeerContext;
148}
149
150bool
152{
153 if (!Security::PeerConnector::initialize(serverSession))
154 return false;
155
156 // client connection supplies TLS client details and is also used if we
157 // need to splice or terminate the client and server connections
158 if (!Comm::IsConnOpen(clientConn))
159 return false;
160
161 if (ConnStateData *csd = request->clientConnectionManager.valid()) {
162
163 SBuf *hostName = nullptr;
164
165 //Enable Status_request TLS extension, required to bump some clients
166 SSL_set_tlsext_status_type(serverSession.get(), TLSEXT_STATUSTYPE_ocsp);
167
168 const Security::TlsDetails::Pointer details = csd->tlsParser.details;
169 if (details && !details->serverName.isEmpty())
170 hostName = new SBuf(details->serverName);
171
172 if (!hostName) {
173 // While we are peeking at the certificate, we may not know the server
174 // name that the client will request (after interception or CONNECT)
175 // unless it was the CONNECT request with a user-typed address.
176 const bool isConnectRequest = !csd->port->flags.isIntercepted();
177 if (!request->flags.sslPeek || isConnectRequest)
178 hostName = new SBuf(request->url.host());
179 }
180
181 if (hostName)
182 SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, (void*)hostName);
183
184 if (csd->sslBumpMode == Ssl::bumpPeek || csd->sslBumpMode == Ssl::bumpStare) {
185 auto clientSession = fd_table[clientConn->fd].ssl.get();
186 Must(clientSession);
187 BIO *bc = SSL_get_rbio(clientSession);
188 Ssl::ClientBio *cltBio = static_cast<Ssl::ClientBio *>(BIO_get_data(bc));
189 Must(cltBio);
190 if (details && details->tlsVersion.protocol != AnyP::PROTO_NONE)
191 applyTlsDetailsToSSL(serverSession.get(), details, csd->sslBumpMode);
192
193 BIO *b = SSL_get_rbio(serverSession.get());
194 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
195 Must(srvBio);
196 // inherit client features such as TLS version and SNI
197 srvBio->setClientFeatures(details, cltBio->rBufData());
198 srvBio->recordInput(true);
199 srvBio->mode(csd->sslBumpMode);
200
201#if defined(SSL_OP_LEGACY_SERVER_CONNECT)
202 // While peeking, Squid is not generating any TLS bytes, but we are
203 // still being driven by OpenSSL negotiation logic. We enable as
204 // many features and workarounds as possible to reduce cases where
205 // OpenSSL refuses to accept a valid TLS server response. This code
206 // assumes that an admin should not expect a peeking Squid to
207 // automatically enforce a particular set of TLS conditions (e.g.,
208 // "no legacy TLS servers"). When that assumption is invalidated, we
209 // will need to add a configuration directive to set peeking TLS
210 // options.
211 if (csd->sslBumpMode == Ssl::bumpPeek && SSL_OP_LEGACY_SERVER_CONNECT) {
212 const auto adjustedOptions = SSL_set_options(serverSession.get(), SSL_OP_LEGACY_SERVER_CONNECT);
213 debugs(83, 5, "post-SSL_OP_LEGACY_SERVER_CONNECT options for session=" << serverSession << ": " << asHex(adjustedOptions));
214 }
215#endif
216 } else {
217 const bool redirected = request->flags.redirected && ::Config.onoff.redir_rewrites_host;
218 const char *sniServer = (!hostName || redirected) ?
219 request->url.host() :
220 hostName->c_str();
221 if (sniServer)
222 setClientSNI(serverSession.get(), sniServer);
223 }
224
225 if (Ssl::ServerBump *serverBump = csd->serverBump()) {
226 serverBump->attachServerSession(serverSession);
227 // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
228 if (X509 *peeked_cert = serverBump->serverCert.get()) {
229 X509_up_ref(peeked_cert);
230 SSL_set_ex_data(serverSession.get(), ssl_ex_index_ssl_peeked_cert, peeked_cert);
231 }
232 }
233 }
234
235 return true;
236}
237
238void
240{
241 // Check the list error with
242 if (!request->clientConnectionManager.valid() || !fd_table[serverConnection()->fd].ssl)
243 return;
244
245 // remember the server certificate from the ErrorDetail object
246 if (Ssl::ServerBump *serverBump = request->clientConnectionManager->serverBump()) {
247 if (!serverBump->serverCert.get()) {
248 // remember the server certificate from the ErrorDetail object
249 const auto errDetail = dynamic_cast<Security::ErrorDetail *>(error ? error->detail.getRaw() : nullptr);
250 if (errDetail && errDetail->peerCert())
251 serverBump->serverCert.resetAndLock(errDetail->peerCert());
252 else {
253 handleServerCertificate();
254 }
255 }
256
257 if (error) {
258 // For intercepted connections, set the host name to the server
259 // certificate CN. Otherwise, we just hope that CONNECT is using
260 // a user-entered address (a host name or a user-entered IP).
261 const bool isConnectRequest = !request->clientConnectionManager->port->flags.isIntercepted();
262 if (request->flags.sslPeek && !isConnectRequest) {
263 if (X509 *srvX509 = serverBump->serverCert.get()) {
264 if (const char *name = Ssl::CommonHostName(srvX509)) {
265 request->url.host(name);
266 debugs(83, 3, "reset request host: " << name);
267 }
268 }
269 }
270 }
271 }
272
273 if (!error) {
274 serverCertificateVerified();
275 if (splice) {
276 if (!Comm::IsConnOpen(clientConn)) {
277 bail(new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al));
278 throw TextException("from-client connection gone", Here());
279 }
280 startTunneling();
281 }
282 }
283}
284
285void
287{
288 // switchToTunnel() drains any already buffered from-server data (rBufData)
289 fd_table[serverConn->fd].useDefaultIo();
290 // tunnelStartShoveling() drains any buffered from-client data (inBuf)
291 fd_table[clientConn->fd].useDefaultIo();
292
293 // TODO: Encapsulate this frequently repeated logic into a method.
294 const auto session = fd_table[serverConn->fd].ssl;
295 auto b = SSL_get_rbio(session.get());
296 auto srvBio = static_cast<Ssl::ServerBio*>(BIO_get_data(b));
297
298 debugs(83, 5, "will tunnel instead of negotiating TLS");
299 switchToTunnel(request.getRaw(), clientConn, serverConn, srvBio->rBufData());
300 answer().tunneled = true;
301 disconnect();
302 callBack();
303}
304
305void
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 || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
314 debugs(81, 3, "hold write on SSL connection on FD " << fd);
315 checkForPeekAndSplice();
316 return;
317 }
318
320}
321
322void
324{
325 const int fd = serverConnection()->fd;
326 Security::SessionPointer session(fd_table[fd].ssl);
327 BIO *b = SSL_get_rbio(session.get());
328 Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
329
330 if (srvBio->bumpMode() == Ssl::bumpPeek) {
331 auto bypassValidator = false;
332 if (srvBio->encryptedCertificates()) {
333 // it is pointless to peek at encrypted certificates
334 //
335 // we currently splice all sessions with encrypted certificates
336 // if (const auto spliceEncryptedCertificates = true) {
337 bypassValidator = true;
338 // } // else fall through to find a matching ssl_bump action (with limited info)
339 } else if (srvBio->resumingSession()) {
340 // In peek mode, the ClientHello message is forwarded to the server.
341 // If the server is resuming a previous (spliced) SSL session with
342 // the client, then probably we are here because our local SSL
343 // object does not know anything about the session being resumed.
344 //
345 // we currently splice all resumed sessions
346 // if (const auto spliceResumed = true) {
347 bypassValidator = true;
348 // } // else fall through to find a matching ssl_bump action (with limited info)
349 }
350
351 if (bypassValidator) {
352 bypassCertValidator();
353 checkForPeekAndSpliceMatched(Ssl::bumpSplice);
354 return;
355 }
356 }
357
358 // If we are in peek-and-splice mode and still we did not write to
359 // server yet, try to see if we should splice.
360 // In this case the connection can be saved.
361 // If the checklist decision is do not splice a new error will
362 // occur in the next SSL_connect call, and we will fail again.
363 // Abort on certificate validation errors to avoid splicing and
364 // thus hiding them.
365 // Abort if no certificate found probably because of malformed or
366 // unsupported server Hello message (TODO: make configurable).
367 // TODO: Add/use a positive "successfully validated server cert" signal
368 // instead of relying on the "![presumably_]validation_error && serverCert"
369 // signal combo.
370 if (!SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail) &&
371 (srvBio->bumpMode() == Ssl::bumpPeek || srvBio->bumpMode() == Ssl::bumpStare) && srvBio->holdWrite()) {
372 Security::CertPointer serverCert(SSL_get_peer_certificate(session.get()));
373 if (serverCert) {
374 debugs(81, 3, "hold TLS write on FD " << fd << " despite " << errorDetail);
375 checkForPeekAndSplice();
376 return;
377 }
378 }
379
380 // else call parent noteNegotiationError to produce an error page
382}
383
384void
386{
387 if (serverCertificateHandled)
388 return;
389
390 if (ConnStateData *csd = request->clientConnectionManager.valid()) {
391 const int fd = serverConnection()->fd;
392 Security::SessionPointer session(fd_table[fd].ssl);
393 Security::CertPointer serverCert(SSL_get_peer_certificate(session.get()));
394 if (!serverCert)
395 return;
396
397 serverCertificateHandled = true;
398
399 // remember the server certificate for later use
400 if (Ssl::ServerBump *serverBump = csd->serverBump()) {
401 serverBump->serverCert = std::move(serverCert);
402 }
403 }
404}
405
406void
408{
409 if (ConnStateData *csd = request->clientConnectionManager.valid()) {
410 Security::CertPointer serverCert;
411 if(Ssl::ServerBump *serverBump = csd->serverBump())
412 serverCert.resetAndLock(serverBump->serverCert.get());
413 else {
414 const int fd = serverConnection()->fd;
415 Security::SessionPointer session(fd_table[fd].ssl);
416 serverCert.resetWithoutLocking(SSL_get_peer_certificate(session.get()));
417 }
418 if (serverCert) {
419 csd->resetSslCommonName(Ssl::CommonHostName(serverCert.get()));
420 debugs(83, 5, "HTTPS server CN: " << csd->sslCommonName() <<
421 " bumped: " << *serverConnection());
422 }
423 }
424}
425
#define CallJobHere1(debugSection, debugLevel, job, Class, method, arg1)
#define Here()
source code location of the caller
Definition Here.h:15
AsHex< Integer > asHex(const Integer n)
a helper to ease AsHex object creation
Definition IoManip.h:169
#define Must(condition)
void error(char *format,...)
void applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl::BumpMode bumpMode)
Definition bio.cc:570
#define CBDATA_NAMESPACED_CLASS_INIT(namespace, type)
Definition cbdata.h:333
static MakingPointer Make(const acl_access *a, HttpRequest *r)
static void NonBlockingCheck(MakingPointer &&p, ACLCB *cb, void *data)
int kind
the matched custom access list verb (or zero)
Definition Acl.h:99
bool allowed() const
Definition Acl.h:82
a smart AsyncCall pointer for delivery of future results
Cbc * valid() const
was set and is valid
Definition CbcPointer.h:41
CbcPointer< ConnStateData > clientConnectionManager
Definition SBuf.h:94
const char * c_str()
Definition SBuf.cc:516
A combination of PeerOptions and the corresponding Context.
void resetWithoutLocking(T *t)
Reset raw pointer - unlock any previous one and save new one without locking.
T * get() const
Returns raw and possibly nullptr pointer.
virtual bool initialize(Security::SessionPointer &)
virtual void noteNegotiationError(const Security::ErrorDetailPointer &)
Called when the SSL_connect function aborts with an SSL negotiation error.
virtual void noteWantWrite()
HttpRequestPointer request
peer connection trigger or cause
const SBuf & rBufData()
The buffered input data.
Definition bio.h:61
A PeerConnector for HTTP origin servers. Capable of SslBumping.
bool initialize(Security::SessionPointer &) override
void checkForPeekAndSpliceDone(Acl::Answer)
Callback function for ssl_bump acl check in step3 SSL bump step.
void noteNegotiationError(const Security::ErrorDetailPointer &) override
Called when the SSL_connect function aborts with an SSL negotiation error.
void startTunneling()
Abruptly stops TLS negotiation and starts tunneling.
void checkForPeekAndSpliceMatched(const Ssl::BumpMode finalMode)
Handles the final bumping decision.
void noteNegotiationDone(ErrorState *error) override
static void cbCheckForPeekAndSpliceDone(Acl::Answer, void *data)
A wrapper function for checkForPeekAndSpliceDone for use with acl.
PeekingPeerConnector(HttpRequestPointer &aRequest, const Comm::ConnectionPointer &aServerConn, const Comm::ConnectionPointer &aClientConn, const AsyncCallback< Security::EncryptorAnswer > &aCallback, const AccessLogEntryPointer &alp, time_t timeout=0)
Security::FuturePeerContext * peerContext() const override
Ssl::BumpMode checkForPeekAndSpliceGuess() const
Guesses the final bumping decision when no ssl_bump rules match.
void mode(Ssl::BumpMode m)
The bumping mode.
Definition bio.h:160
bool holdWrite() const
The write hold state.
Definition bio.h:150
void recordInput(bool r)
Enables or disables the input data recording, for internal analysis.
Definition bio.h:154
bool canBump()
Whether we can bump or not the SSL stream.
Definition bio.h:158
void setClientFeatures(Security::TlsDetails::Pointer const &details, SBuf const &hello)
Sets the random number to use in client SSL HELLO message.
Definition bio.cc:264
bool encryptedCertificates() const
Definition bio.cc:439
Ssl::BumpMode bumpMode()
return the bumping mode
Definition bio.h:161
bool resumingSession()
Definition bio.cc:433
bool canSplice()
Whether we can splice or not the SSL stream.
Definition bio.h:156
an std::runtime_error with thrower location info
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
@ ERR_SECURE_CONNECT_FAIL
Definition forward.h:31
@ ERR_GATEWAY_FAILURE
Definition forward.h:67
#define fd_table
Definition fde.h:189
int ssl_ex_index_server
int ssl_ex_index_ssl_peeked_cert
int ssl_ex_index_ssl_error_detail
@ ACCESS_ALLOWED
Definition Acl.h:42
void setClientSNI(SSL *ssl, const char *fqdn)
Definition support.cc:1166
const char * CommonHostName(X509 *x509)
Definition gadgets.cc:1073
BumpMode
Definition support.h:132
@ bumpTerminate
Definition support.h:132
@ bumpPeek
Definition support.h:132
@ bumpClientFirst
Definition support.h:132
@ bumpNone
Definition support.h:132
@ bumpStare
Definition support.h:132
@ bumpSplice
Definition support.h:132
@ bumpBump
Definition support.h:132
@ bumpServerFirst
Definition support.h:132
@ PROTO_NONE
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition Connection.cc:27
@ scForbidden
Definition StatusCode.h:48
@ scInternalServerError
Definition StatusCode.h:73
Network/connection security abstraction layer.
Definition Connection.h:34
std::shared_ptr< SSL > SessionPointer
Definition Session.h:53
Definition Xaction.cc:40
void * BIO_get_data(BIO *table)
Definition openssl.h:62
void switchToTunnel(HttpRequest *request, const Comm::ConnectionPointer &clientConn, const Comm::ConnectionPointer &srvConn, const SBuf &preReadServerData)
Definition tunnel.cc:1615