PeekingPeerConnector.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 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"
20 #include "SquidConfig.h"
21 #include "ssl/bio.h"
23 #include "ssl/ServerBump.h"
24 #include "tunnel.h"
25 
26 CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector);
27 
29  const Comm::ConnectionPointer &aServerConn,
30  const Comm::ConnectionPointer &aClientConn,
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
52 {
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
60 {
61  const Ssl::BumpMode finalAction = aclAnswer.allowed() ?
62  static_cast<Ssl::BumpMode>(aclAnswer.kind):
63  checkForPeekAndSpliceGuess();
64  checkForPeekAndSpliceMatched(finalAction);
65 }
66 
67 void
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);
88 }
89 
90 void
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);
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 
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 
145 {
147 }
148 
149 bool
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
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
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
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 
303 }
304 
305 void
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
365 }
366 
367 void
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
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 
bool holdWrite() const
The write hold state.
Definition: bio.h:150
@ bumpPeek
Definition: support.h:132
@ ERR_SECURE_CONNECT_FAIL
Definition: forward.h:31
void startTunneling()
Abruptly stops TLS negotiation and starts tunneling.
#define Here()
source code location of the caller
Definition: Here.h:15
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
virtual bool initialize(Security::SessionPointer &)
@ ERR_GATEWAY_FAILURE
Definition: forward.h:67
const char * CommonHostName(X509 *x509)
Definition: gadgets.cc:1027
void noteNegotiationDone(ErrorState *error) override
@ PROTO_NONE
Definition: ProtocolType.h:24
void applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl::BumpMode bumpMode)
Definition: bio.cc:570
HttpRequestPointer request
peer connection trigger or cause
void error(char *format,...)
Security::FuturePeerContext * defaultPeerContext
Definition: SquidConfig.h:506
Definition: SBuf.h:93
BumpMode
Definition: support.h:132
Ssl::BumpMode checkForPeekAndSpliceGuess() const
Guesses the final bumping decision when no ssl_bump rules match.
@ bumpTerminate
Definition: support.h:132
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition: Connection.cc:27
A combination of PeerOptions and the corresponding Context.
Definition: PeerOptions.h:154
A PeerConnector for HTTP origin servers. Capable of SslBumping.
void * BIO_get_data(BIO *table)
Definition: openssl.h:62
struct SquidConfig::@106 ssl_client
CBDATA_NAMESPACED_CLASS_INIT(Ssl, PeekingPeerConnector)
static MakingPointer Make(const acl_access *a, HttpRequest *r)
@ bumpServerFirst
Definition: support.h:132
PeekingPeerConnector(HttpRequestPointer &aRequest, const Comm::ConnectionPointer &aServerConn, const Comm::ConnectionPointer &aClientConn, const AsyncCallback< Security::EncryptorAnswer > &aCallback, const AccessLogEntryPointer &alp, time_t timeout=0)
@ scForbidden
Definition: StatusCode.h:48
int ssl_ex_index_ssl_peeked_cert
virtual void noteNegotiationError(const Security::ErrorDetailPointer &)
Called when the SSL_connect function aborts with an SSL negotiation error.
Definition: Xaction.cc:39
static void NonBlockingCheck(MakingPointer &&p, ACLCB *cb, void *data)
void switchToTunnel(HttpRequest *request, const Comm::ConnectionPointer &clientConn, const Comm::ConnectionPointer &srvConn, const SBuf &preReadServerData)
Definition: tunnel.cc:1540
Security::FuturePeerContext * peerContext() const override
bool canSplice()
Whether we can splice or not the SSL stream.
Definition: bio.h:156
bool resumingSession()
Definition: bio.cc:433
@ scInternalServerError
Definition: StatusCode.h:73
@ bumpStare
Definition: support.h:132
int ssl_ex_index_server
void recordInput(bool r)
Enables or disables the input data recording, for internal analysis.
Definition: bio.h:154
void mode(Ssl::BumpMode m)
The bumping mode.
Definition: bio.h:160
#define fd_table
Definition: fde.h:189
bool encryptedCertificates() const
Definition: bio.cc:439
std::shared_ptr< SSL > SessionPointer
Definition: Session.h:53
bool allowed() const
Definition: Acl.h:82
void checkForPeekAndSpliceDone(Acl::Answer)
Callback function for ssl_bump acl check in step3 SSL bump step.
static void cbCheckForPeekAndSpliceDone(Acl::Answer, void *data)
A wrapper function for checkForPeekAndSpliceDone for use with acl.
@ bumpNone
Definition: support.h:132
bool initialize(Security::SessionPointer &) override
bool canBump()
Whether we can bump or not the SSL stream.
Definition: bio.h:158
an std::runtime_error with thrower location info
Definition: TextException.h:20
void checkForPeekAndSpliceMatched(const Ssl::BumpMode finalMode)
Handles the final bumping decision.
@ bumpBump
Definition: support.h:132
#define Must(condition)
Definition: TextException.h:75
@ ACCESS_ALLOWED
Definition: Acl.h:42
int kind
the matched custom access list verb (or zero)
Definition: Acl.h:99
void noteNegotiationError(const Security::ErrorDetailPointer &) override
Called when the SSL_connect function aborts with an SSL negotiation error.
@ bumpClientFirst
Definition: support.h:132
Network/connection security abstraction layer.
Definition: Connection.h:33
virtual void noteWantWrite()
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192
void resetWithoutLocking(T *t)
Reset raw pointer - unlock any previous one and save new one without locking.
int ssl_ex_index_ssl_error_detail
CbcPointer< ConnStateData > clientConnectionManager
Definition: HttpRequest.h:232
void setClientSNI(SSL *ssl, const char *fqdn)
Definition: support.cc:1161
T * get() const
Returns raw and possibly nullptr pointer.
class SquidConfig Config
Definition: SquidConfig.cc:12
#define CallJobHere1(debugSection, debugLevel, job, Class, method, arg1)
Definition: AsyncJobCalls.h:64
@ bumpSplice
Definition: support.h:132

 

Introduction

Documentation

Support

Miscellaneous