2 * Copyright (C) 1996-2025 The Squid Software Foundation and contributors
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.
9 /* DEBUG: section 83 SSL-Bump Server/Peer negotiation */
12 #include "acl/FilledChecklist.h"
13 #include "client_side.h"
14 #include "errorpage.h"
16 #include "http/Stream.h"
17 #include "HttpRequest.h"
18 #include "security/ErrorDetail.h"
19 #include "security/NegotiationHistory.h"
20 #include "SquidConfig.h"
22 #include "ssl/PeekingPeerConnector.h"
23 #include "ssl/ServerBump.h"
26 CBDATA_NAMESPACED_CLASS_INIT(Ssl
, PeekingPeerConnector
);
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
),
38 serverCertificateHandled(false)
42 if (const auto csd
= request
->clientConnectionManager
.valid()) {
43 const auto serverBump
= csd
->serverBump();
45 Must(serverBump
->at(XactionStep::tlsBump3
));
47 // else the client is gone, and we cannot check the step, but must carry on
51 Ssl::PeekingPeerConnector::cbCheckForPeekAndSpliceDone(const Acl::Answer aclAnswer
, void *data
)
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
);
59 Ssl::PeekingPeerConnector::checkForPeekAndSpliceDone(const Acl::Answer aclAnswer
)
61 const Ssl::BumpMode finalAction
= aclAnswer
.allowed() ?
62 static_cast<Ssl::BumpMode
>(aclAnswer
.kind
):
63 checkForPeekAndSpliceGuess();
64 checkForPeekAndSpliceMatched(finalAction
);
68 Ssl::PeekingPeerConnector::checkForPeekAndSplice()
70 handleServerCertificate();
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);
91 Ssl::PeekingPeerConnector::checkForPeekAndSpliceMatched(const Ssl::BumpMode action
)
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
);
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
;
105 al
->ssl
.bumpMode
= finalAction
;
107 if (finalAction
== Ssl::bumpTerminate
) {
108 bail(new ErrorState(ERR_SECURE_CONNECT_FAIL
, Http::scForbidden
, request
.getRaw(), al
));
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();
119 // Ssl Negotiation stops here. Last SSL checks for valid certificates
120 // and if done, switch to tunnel mode
121 if (sslFinalized() && callback
)
127 Ssl::PeekingPeerConnector::checkForPeekAndSpliceGuess() const
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
;
135 debugs(83,5, "default to splicing after " << currentMode
);
137 debugs(83,3, "default to splicing due to missing info");
140 return Ssl::bumpSplice
;
143 Security::FuturePeerContext
*
144 Ssl::PeekingPeerConnector::peerContext() const
146 return ::Config
.ssl_client
.defaultPeerContext
;
150 Ssl::PeekingPeerConnector::initialize(Security::SessionPointer
&serverSession
)
152 if (!Security::PeerConnector::initialize(serverSession
))
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
))
160 if (ConnStateData
*csd
= request
->clientConnectionManager
.valid()) {
162 SBuf
*hostName
= nullptr;
164 //Enable Status_request TLS extension, required to bump some clients
165 SSL_set_tlsext_status_type(serverSession
.get(), TLSEXT_STATUSTYPE_ocsp
);
167 const Security::TlsDetails::Pointer details
= csd
->tlsParser
.details
;
168 if (details
&& !details
->serverName
.isEmpty())
169 hostName
= new SBuf(details
->serverName
);
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());
181 SSL_set_ex_data(serverSession
.get(), ssl_ex_index_server
, (void*)hostName
);
183 if (csd
->sslBumpMode
== Ssl::bumpPeek
|| csd
->sslBumpMode
== Ssl::bumpStare
) {
184 auto clientSession
= fd_table
[clientConn
->fd
].ssl
.get();
186 BIO
*bc
= SSL_get_rbio(clientSession
);
187 Ssl::ClientBio
*cltBio
= static_cast<Ssl::ClientBio
*>(BIO_get_data(bc
));
189 if (details
&& details
->tlsVersion
.protocol
!= AnyP::PROTO_NONE
)
190 applyTlsDetailsToSSL(serverSession
.get(), details
, csd
->sslBumpMode
);
192 BIO
*b
= SSL_get_rbio(serverSession
.get());
193 Ssl::ServerBio
*srvBio
= static_cast<Ssl::ServerBio
*>(BIO_get_data(b
));
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
);
200 const bool redirected
= request
->flags
.redirected
&& ::Config
.onoff
.redir_rewrites_host
;
201 const char *sniServer
= (!hostName
|| redirected
) ?
202 request
->url
.host() :
205 setClientSNI(serverSession
.get(), sniServer
);
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
);
222 Ssl::PeekingPeerConnector::noteNegotiationDone(ErrorState
*error
)
224 // Check the list error with
225 if (!request
->clientConnectionManager
.valid() || !fd_table
[serverConnection()->fd
].ssl
)
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());
236 handleServerCertificate();
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
);
257 serverCertificateVerified();
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());
269 Ssl::PeekingPeerConnector::startTunneling()
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();
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
));
281 debugs(83, 5, "will tunnel instead of negotiating TLS");
282 switchToTunnel(request
.getRaw(), clientConn
, serverConn
, srvBio
->rBufData());
283 answer().tunneled
= true;
289 Ssl::PeekingPeerConnector::noteWantWrite()
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
));
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();
302 Security::PeerConnector::noteWantWrite();
306 Ssl::PeekingPeerConnector::noteNegotiationError(const Security::ErrorDetailPointer
&errorDetail
)
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
));
313 if (srvBio
->bumpMode() == Ssl::bumpPeek
) {
314 auto bypassValidator
= false;
315 if (srvBio
->encryptedCertificates()) {
316 // it is pointless to peek at encrypted certificates
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.
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)
334 if (bypassValidator
) {
335 bypassCertValidator();
336 checkForPeekAndSpliceMatched(Ssl::bumpSplice
);
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
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"
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()));
357 debugs(81, 3, "hold TLS write on FD " << fd
<< " despite " << errorDetail
);
358 checkForPeekAndSplice();
363 // else call parent noteNegotiationError to produce an error page
364 Security::PeerConnector::noteNegotiationError(errorDetail
);
368 Ssl::PeekingPeerConnector::handleServerCertificate()
370 if (serverCertificateHandled
)
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()));
380 serverCertificateHandled
= true;
382 // remember the server certificate for later use
383 if (Ssl::ServerBump
*serverBump
= csd
->serverBump()) {
384 serverBump
->serverCert
= std::move(serverCert
);
390 Ssl::PeekingPeerConnector::serverCertificateVerified()
392 if (ConnStateData
*csd
= request
->clientConnectionManager
.valid()) {
393 Security::CertPointer serverCert
;
394 if(Ssl::ServerBump
*serverBump
= csd
->serverBump())
395 serverCert
.resetAndLock(serverBump
->serverCert
.get());
397 const int fd
= serverConnection()->fd
;
398 Security::SessionPointer
session(fd_table
[fd
].ssl
);
399 serverCert
.resetWithoutLocking(SSL_get_peer_certificate(session
.get()));
402 csd
->resetSslCommonName(Ssl::CommonHostName(serverCert
.get()));
403 debugs(83, 5, "HTTPS server CN: " << csd
->sslCommonName() <<
404 " bumped: " << *serverConnection());