static void free_http_port_list(http_port_list **);
#if USE_SSL
- static void parse_https_port_list(https_port_list **);
- static void dump_https_port_list(StoreEntry *, const char *, const https_port_list *);
- static void free_https_port_list(https_port_list **);
+ #define parse_https_port_list(l) parsePortList((l),"https")
+ #define dump_https_port_list(e,n,l) dump_http_port_list((e),(n),(l))
+ #define free_https_port_list(l) free_http_port_list((l))
+ #define check_null_https_port_list(l) check_null_http_port_list((l))
+static void parse_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign);
+static void dump_sslproxy_cert_sign(StoreEntry *entry, const char *name, sslproxy_cert_sign *cert_sign);
+static void free_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign);
+static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt);
+static void dump_sslproxy_cert_adapt(StoreEntry *entry, const char *name, sslproxy_cert_adapt *cert_adapt);
+static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt);
- #if 0
- static int check_null_https_port_list(const https_port_list *);
- #endif
#endif /* USE_SSL */
static void parse_b_size_t(size_t * var);
http_port_list *s;
for (s = Config.Sockaddr.http; s != NULL; s = (http_port_list *) s->next) {
- if (!s->cert && !s->key)
+ if (!s->cert)
continue;
- debugs(3, 1, "Initializing http_port " << s->http.s << " SSL context");
+ debugs(3, 1, "Initializing http_port " << s->s << " SSL context");
s->staticSslContext.reset(
sslCreateServerContext(s->cert, s->key,
s->sslContextSessionId));
Ssl::readCertChainAndPrivateKeyFromFiles(s->signingCert, s->signPkey, s->certsToChain, s->cert, s->key);
- debugs(3, DBG_IMPORTANT, "No SSL private key configured for http_port " << s->http.s);
+
+ if (!s->signPkey)
++ debugs(3, DBG_IMPORTANT, "No SSL private key configured for http_port " << s->s);
+
+ Ssl::generateUntrustedCert(s->untrustedSigningCert, s->untrustedSignPkey,
+ s->signingCert, s->signPkey);
}
}
s->version, s->cipher, s->options, s->sslflags, s->clientca,
s->cafile, s->capath, s->crlfile, s->dhfile,
s->sslContextSessionId));
- debugs(3, DBG_IMPORTANT, "No SSL private key configured for https_port " << s->http.s);
+
+ if (s->cert && s->sslBump) {
+ Ssl::readCertChainAndPrivateKeyFromFiles(s->signingCert, s->signPkey, s->certsToChain, s->cert, s->key);
+ if (!s->signPkey)
++ debugs(3, DBG_IMPORTANT, "No SSL private key configured for https_port " << s->s);
+
+ Ssl::generateUntrustedCert(s->untrustedSigningCert, s->untrustedSignPkey,
+ s->signingCert, s->signPkey);
+ }
}
}
parse_http_port_option(s, token);
}
++#if USE_SSL
++ if (strcasecmp(protocol, "https") == 0) {
++ /* ssl-bump on https_port configuration requires either tproxy or intercepted, and vice versa */
++ const bool hijacked = s->spoof_client_ip || s->intercepted;
++ if (s->sslBump && !hijacked) {
++ debugs(3, DBG_CRITICAL, "FATAL: ssl-bump on https_port requires tproxy/intercepted which is missing.");
++ self_destruct();
++ }
++ if (hijacked && !s->sslBump) {
++ debugs(3, DBG_CRITICAL, "FATAL: tproxy/intercepted on https_port requires ssl-bump which is missing.");
++ self_destruct();
++ }
++ }
++#endif
++
if (Ip::EnableIpv6&IPV6_SPECIAL_SPLITSTACK && s->s.IsAnyAddr()) {
// clone the port options from *s to *(s->next)
s->next = clone_http_port_list(s);
You may specify multiple socket addresses on multiple lines,
each with their own SSL certificate and/or options.
+ Modes:
+
+ accel Accelerator / reverse proxy mode
+
+ tproxy Support Linux TPROXY for spoofing outgoing
+ connections using the client IP address.
+ NP: disables authentication and maybe IPv6 on the port.
+
+ ssl-bump Intercept each SSL request matching ssl_bump ACL,
+ establish secure connection with the client and with
+ the server, decrypt HTTP messages as they pass through
+ Squid, and treat them as unencrypted HTTP messages,
+ becoming the man-in-the-middle.
+
+ The ssl_bump option is required to fully enable
+ the SslBump feature.
+
+ Requires tproxy.
+
+ Omitting the mode flag causes default forward proxy mode to be used.
+
+
+ See http_port for a list of generic options
+
+
+ SSL Options:
+
+ cert= Path to SSL certificate (PEM format).
+
+ key= Path to SSL private key file (PEM format)
+ if not specified, the certificate file is
+ assumed to be a combined certificate and
+ key file.
+
+ version= The version of SSL/TLS supported
+ 1 automatic (default)
+ 2 SSLv2 only
+ 3 SSLv3 only
+ 4 TLSv1 only
+
+ cipher= Colon separated list of supported ciphers.
+
+ options= Various SSL engine options. The most important
+ being:
+ NO_SSLv2 Disallow the use of SSLv2
+ NO_SSLv3 Disallow the use of SSLv3
+ NO_TLSv1 Disallow the use of TLSv1
+ SINGLE_DH_USE Always create a new key when using
+ temporary/ephemeral DH key exchanges
+ See src/ssl_support.c or OpenSSL SSL_CTX_set_options
+ documentation for a complete list of options.
+
+ clientca= File containing the list of CAs to use when
+ requesting a client certificate.
+
+ cafile= File containing additional CA certificates to
+ use when verifying client certificates. If unset
+ clientca will be used.
+
+ capath= Directory containing additional CA certificates
+ and CRL lists to use when verifying client certificates.
+
+ crlfile= File of additional CRL lists to use when verifying
+ the client certificate, in addition to CRLs stored in
+ the capath. Implies VERIFY_CRL flag below.
+
+ dhparams= File containing DH parameters for temporary/ephemeral
+ DH key exchanges.
+
+ sslflags= Various flags modifying the use of SSL:
+ DELAYED_AUTH
+ Don't request client certificates
+ immediately, but wait until acl processing
+ requires a certificate (not yet implemented).
+ NO_DEFAULT_CA
+ Don't use the default CA lists built in
+ to OpenSSL.
+ NO_SESSION_REUSE
+ Don't allow for session reuse. Each connection
+ will result in a new SSL session.
+ VERIFY_CRL
+ Verify CRL lists when accepting client
+ certificates.
+ VERIFY_CRL_ALL
+ Verify CRL lists for all certificates in the
+ client certificate chain.
+
+ sslcontext= SSL session ID context identifier.
+
+ generate-host-certificates[=<on|off>]
+ Dynamically create SSL server certificates for the
+ destination hosts of bumped SSL requests.When
+ enabled, the cert and key options are used to sign
+ generated certificates. Otherwise generated
+ certificate will be selfsigned.
+ If there is CA certificate life time of generated
+ certificate equals lifetime of CA certificate. If
+ generated certificate is selfsigned lifetime is three
+ years.
+ This option is enabled by default when SslBump is used.
+ See the sslBump option above for more information.
+
+ dynamic_cert_mem_cache_size=SIZE
+ Approximate total RAM size spent on cached generated
+ certificates. If set to zero, caching is disabled. The
+ default value is 4MB. An average XXX-bit certificate
+ consumes about XXX bytes of RAM.
+
+ See http_port for a list of available options.
DOC_END
NAME: tcp_outgoing_tos tcp_outgoing_ds tcp_outgoing_dscp
readSomeData();
}
- ErrorState *err = errorCon(ERR_SECURE_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
+void
+ConnStateData::quitAfterError(HttpRequest *request)
+{
+ // From HTTP p.o.v., we do not have to close after every error detected
+ // at the client-side, but many such errors do require closure and the
+ // client-side code is bad at handling errors so we play it safe.
+ if (request)
+ request->flags.proxy_keepalive = 0;
+ flags.readMore = false;
+ debugs(33,4, HERE << "Will close after error: " << clientConnection);
+}
+
+#if USE_SSL
+bool ConnStateData::serveDelayedError(ClientSocketContext *context)
+{
+ ClientHttpRequest *http = context->http;
+ StoreEntry *e = bumpServerFirstErrorEntry();
+ if (!e)
+ return false;
+
+ if (!e->isEmpty()) {
+ quitAfterError(http->request);
+
+ //Failed? Here we should get the error from conn and send it to client
+ // The error stored in ConnStateData::bumpFirstEntry, replace the
+ // ClientHttpRequest store entry with this.
+ clientStreamNode *node = context->getClientReplyContext();
+ clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
+ assert (repContext);
+ debugs(33, 5, "Connection first has failed for " << http->uri << ". Respond with an error");
+ repContext->setReplyToStoreEntry(e);
+ context->pullData();
+ return true;
+ }
+
+ // We are in ssl-bump first mode. We have not the server connect name when
+ // we connected to server so we have to check certificates subject with our server name
+ if (X509 *server_cert = getBumpServerCert()) {
+ HttpRequest *request = http->request;
+ if (!Ssl::checkX509ServerValidity(server_cert, request->GetHost())) {
+ debugs(33, 2, "SQUID_X509_V_ERR_DOMAIN_MISMATCH: Certificate does not match domainname " << request->GetHost());
+
+ ACLFilledChecklist check(Config.ssl_client.cert_error, request, dash_str);
+ check.sslErrorList = new Ssl::Errors(SQUID_X509_V_ERR_DOMAIN_MISMATCH);
+ if (Comm::IsConnOpen(pinning.serverConnection))
+ check.fd(pinning.serverConnection->fd);
+ bool allowDomainMismatch = (check.fastCheck() == ACCESS_ALLOWED);
+ delete check.sslErrorList;
+ check.sslErrorList = NULL;
+
+ if (!allowDomainMismatch) {
+ quitAfterError(request);
+
+ clientStreamNode *node = context->getClientReplyContext();
+ clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
+ assert (repContext);
+
+ // Create an error object and fill it
++ ErrorState *err = new ErrorState(ERR_SECURE_CONNECT_FAIL, HTTP_SERVICE_UNAVAILABLE, request);
++
+ err->src_addr = clientConnection->remote;
+#ifdef EPROTO
+ err->xerrno = EPROTO;
+#else
+ err->xerrno = EACCES;
+#endif
+ Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail( SQUID_X509_V_ERR_DOMAIN_MISMATCH, server_cert, NULL);
+ err->detail = errDetail;
+ repContext->setReplyToError(request->method, err);
+ assert(context->http->out.offset == 0);
+ context->pullData();
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+#endif //USE_SSL
+
static void
clientProcessRequest(ConnStateData *conn, HttpParser *hp, ClientSocketContext *context, const HttpRequestMethod& method, HttpVersion http_ver)
{
request->flags.accelerated = http->flags.accel;
request->flags.sslBumped = conn->switchedToHttps();
+ request->flags.canRePin = request->flags.sslBumped && conn->pinning.pinned;
request->flags.ignore_cc = conn->port->ignore_cc;
- request->flags.no_direct = request->flags.accelerated ? !conn->port->allow_direct : 0;
+ // TODO: decouple http->flags.accel from request->flags.sslBumped
+ request->flags.no_direct = (request->flags.accelerated && !request->flags.sslBumped) ?
+ !conn->port->allow_direct : 0;
+ #if USE_AUTH
+ if (request->flags.sslBumped) {
+ if (conn->auth_user_request != NULL)
+ request->auth_user_request = conn->auth_user_request;
+ }
+ #endif
/** \par
* If transparent or interception mode is working clone the transparent and interception flags
incoming_sockets_accepted++;
// Socket is ready, setup the connection manager to start using it
- ConnStateData *connState = connStateCreate(params.conn, &s->http);
+ ConnStateData *connState = connStateCreate(params.conn, s);
- typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
- AsyncCall::Pointer timeoutCall = JobCallback(33, 5,
- TimeoutDialer, connState, ConnStateData::requestTimeout);
- commSetConnTimeout(params.conn, Config.Timeout.request, timeoutCall);
+ if (s->sslBump) {
+ debugs(33, 5, "httpsAccept: accept transparent connection: " << params.conn);
- Comm::SetSelect(params.conn->fd, COMM_SELECT_READ, clientNegotiateSSL, connState, 0);
+ if (!Config.accessList.ssl_bump) {
+ httpsSslBumpAccessCheckDone(ACCESS_DENIED, connState);
+ return;
+ }
+
+ // Create a fake HTTP request for ssl_bump ACL check,
+ // using tproxy-provided destination IP and port.
+ HttpRequest *request = new HttpRequest();
+ static char ip[MAX_IPSTRLEN];
+ assert(params.conn->flags & (COMM_TRANSPARENT | COMM_INTERCEPTION));
+ request->SetHost(params.conn->local.NtoA(ip, sizeof(ip)));
+ request->port = params.conn->local.GetPort();
+ request->myportname = s->name;
+
+ ACLFilledChecklist *acl_checklist = new ACLFilledChecklist(Config.accessList.ssl_bump, request, NULL);
+ acl_checklist->src_addr = params.conn->remote;
+ acl_checklist->my_addr = s->s;
+ acl_checklist->nonBlockingCheck(httpsSslBumpAccessCheckDone, connState);
+ return;
+ } else {
+ SSL_CTX *sslContext = s->staticSslContext.get();
+ httpsEstablish(connState, sslContext);
+ }
}
void
continue;
}
- debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << s->protocol << "_port " << s->http.s);
+ // TODO: merge with similar code in clientHttpConnectionsOpen()
+ if (s->sslBump && !Config.accessList.ssl_bump) {
- debugs(1, DBG_IMPORTANT, "Will not bump SSL at http_port " << s->http.s << " due to SSL initialization failure.");
++ debugs(33, DBG_IMPORTANT, "WARNING: No ssl_bump configured. Disabling ssl-bump on " << s->protocol << "_port " << s->s);
+ s->sslBump = 0;
+ }
+
+ if (s->sslBump && !s->staticSslContext && !s->generateHostCertificates) {
++ debugs(1, DBG_IMPORTANT, "Will not bump SSL at http_port " << s->s << " due to SSL initialization failure.");
+ s->sslBump = 0;
+ }
+
+ if (s->sslBump) {
+ // Create ssl_ctx cache for this port.
+ Ssl::TheGlobalContextStorage.addLocalStorage(s->s, s->dynamicCertMemCacheSize == std::numeric_limits<size_t>::max() ? 4194304 : s->dynamicCertMemCacheSize);
+ }
+
// Fill out a Comm::Connection which IPC will open as a listener for us
- s->http.listenConn = new Comm::Connection;
- s->http.listenConn->local = s->http.s;
- s->http.listenConn->flags = COMM_NONBLOCKING | (s->http.spoof_client_ip ? COMM_TRANSPARENT : 0) |
- (s->http.intercepted ? COMM_INTERCEPTION : 0);
+ s->listenConn = new Comm::Connection;
+ s->listenConn->local = s->s;
+ s->listenConn->flags = COMM_NONBLOCKING | (s->spoof_client_ip ? COMM_TRANSPARENT : 0) |
+ (s->intercepted ? COMM_INTERCEPTION : 0);
// setup the subscriptions such that new connections accepted by listenConn are handled by HTTPS
typedef CommCbFunPtrCallT<CommAcceptCbPtrFun> AcceptCall;
ConnStateData::ConnStateData() :
AsyncJob("ConnStateData"),
- closing_(false)
+#if USE_SSL
- ,switchedToHttps_(false)
+ switchedToHttps_(false),
+#endif
+ stoppedSending_(NULL),
+ stoppedReceiving_(NULL)
{
pinning.pinned = false;
pinning.auth = false;
// XXX: CBDATA plays with public/private and leaves the following 'private' fields all public... :(
CBDATA_CLASS2(ConnStateData);
- bool closing_;
+#if USE_SSL
bool switchedToHttps_;
+ /// The SSL server host name appears in CONNECT request or the server ip address for the intercepted requests
+ String sslConnectHostOrIp; ///< The SSL server host name as passed in the CONNECT request
+ String sslCommonName; ///< CN name for SSL certificate generation
+ String sslBumpCertKey; ///< Key to use to store/retrieve generated certificate
+
+ /// a job that connects to the HTTPS server to get its SSL certificate
+ CbcPointer<Ssl::ServerPeeker> httpsPeeker;
+ StoreEntry *bumpErrorEntry;
+ Ssl::X509_Pointer bumpServerCert;
+ Ssl::Errors *bumpSslErrorNoList; ///< The list of SSL certificate errors which ignored
+ Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use
+#endif
- String sslHostName; ///< Host name for SSL certificate generation
+ /// the reason why we no longer write the response or nil
+ const char *stoppedSending_;
+ /// the reason why we no longer read the request or nil
+ const char *stoppedReceiving_;
+
AsyncCall::Pointer reader; ///< set when we are reading
BodyPipe::Pointer bodyPipe; // set when we are reading request body
};
void identifyFoundObject(StoreEntry *entry);
int storeOKTransferDone() const;
int storeNotOKTransferDone() const;
+ /// Replaces the store entry with the given and awaiting the client side to read it
+ void setReplyToStoreEntry(StoreEntry *entry);
void setReplyToError(err_type, http_status, const HttpRequestMethod&, char const *, Ip::Address &, HttpRequest *, const char *,
#if USE_AUTH
- AuthUserRequest::Pointer);
+ Auth::UserRequest::Pointer);
#else
void * unused);
#endif
return;
}
- getConn()->switchToHttps(request->GetHost());
+ // We lack HttpReply which logRequest() uses to log the status code.
+ // TODO: Use HttpReply instead of the "200 Connection established" string.
+ al.http.code = 200;
+
+ #if USE_AUTH
+ // Preserve authentication info for the ssl-bumped request
+ if (request->auth_user_request != NULL)
+ getConn()->auth_user_request = request->auth_user_request;
+ #endif
++
+ getConn()->switchToHttps(request->GetHost(), request->port);
}
void
// Bug 3243: CVE 2009-0801
// Bypass of browser same-origin access control in intercepted communication
// To resolve this we must force DIRECT and only to the original client destination.
- if (Config.onoff.client_dst_passthru && request && !request->flags.redirected &&
- (request->flags.intercepted || request->flags.spoof_client_ip)) {
+ const bool isIntercepted = request && !request->flags.redirected && (request->flags.intercepted || request->flags.spoof_client_ip);
+ const bool useOriginalDst = Config.onoff.client_dst_passthru || (request && !request->flags.hostVerified);
+ if (isIntercepted && useOriginalDst) {
- Comm::ConnectionPointer p = new Comm::Connection();
- p->remote = clientConn->local;
- p->peerType = ORIGINAL_DST;
- getOutgoingAddress(request, p);
- serverDestinations.push_back(p);
-
+ selectPeerForIntercepted();
-
// destination "found". continue with the forwarding.
startConnectionOrFail();
} else {
--- /dev/null
- #include "config.h"
+/*
+ * $Id$
+ *
+ * DEBUG: section 33 Client-side Routines
+ *
+ */
+
++#include "squid.h"
+
+#include "client_side.h"
+#include "forward.h"
+#include "ssl/ServerPeeker.h"
+#include "Store.h"
+
+
+CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerPeeker);
+
+
+Ssl::ServerPeeker::ServerPeeker(ConnStateData *anInitiator,
+ const char *host, const int port):
+ AsyncJob("Ssl::ServerPeeker"),
+ initiator(anInitiator),
+ clientConnection(anInitiator->clientConnection),
+ request(new HttpRequest)
+{
+ debugs(33, 4, HERE << "will peek at " << host << ':' << port);
+ request->flags.sslPeek = 1;
+ request->SetHost(host);
+ request->port = port;
+ request->protocol = AnyP::PROTO_HTTPS;
+ request->clientConnectionManager = initiator;
+ const char *uri = urlCanonical(request);
+ entry = storeCreateEntry(uri, uri, request->flags, request->method);
+}
+
+Ssl::ServerPeeker::~ServerPeeker()
+{
+ if (entry)
+ entry->unlock();
+}
+
+void
+Ssl::ServerPeeker::start()
+{
+ FwdState::fwdStart(clientConnection, entry, request);
+}
+
+void Ssl::ServerPeeker::noteHttpsPeeked(Comm::ConnectionPointer &serverConnection)
+{
+ assert(initiator.raw());
+ initiator.clear(); // will trigger the end of the job
+}
+
+bool
+Ssl::ServerPeeker::doneAll() const
+{
+ return !initiator.valid() && AsyncJob::doneAll();
+}
+
+void
+Ssl::ServerPeeker::swanSong()
+{
+}
* $Id$
*/
- #include "config.h"
+ #include "squid.h"
+#include "ssl/gadgets.h"
#include "ssl/crtd_message.h"
#if HAVE_CSTDLIB
#include <cstdlib>
struct request_flags {
- request_flags(): range(0),nocache(0),ims(0),auth(0),cachable(0),hierarchical(0),loopdetect(0),proxy_keepalive(0),proxying(0),refresh(0),redirected(0),need_validation(0),fail_on_validation_err(0),stale_if_hit(0),accelerated(0),ignore_cc(0),intercepted(0),spoof_client_ip(0),internal(0),internalclient(0),must_keepalive(0),pinned(0),canRePin(0),chunked_reply(0),stream_error(0),sslPeek(0),sslBumped(0),destinationIPLookedUp_(0) {
- request_flags(): range(0),nocache(0),ims(0),auth(0),cachable(0),hierarchical(0),loopdetect(0),proxy_keepalive(0),proxying(0),refresh(0),redirected(0),need_validation(0),fail_on_validation_err(0),stale_if_hit(0),accelerated(0),ignore_cc(0),intercepted(0),
- hostVerified(0),
- spoof_client_ip(0),internal(0),internalclient(0),must_keepalive(0),chunked_reply(0),stream_error(0),sslBumped(0),destinationIPLookedUp_(0) {
++ request_flags(): range(0),nocache(0),ims(0),auth(0),cachable(0),hierarchical(0),loopdetect(0),proxy_keepalive(0),proxying(0),refresh(0),redirected(0),need_validation(0),fail_on_validation_err(0),stale_if_hit(0),accelerated(0),ignore_cc(0),intercepted(0),hostVerified(0),spoof_client_ip(0),internal(0),internalclient(0),must_keepalive(0),pinned(0),canRePin(0),chunked_reply(0),stream_error(0),sslPeek(0),sslBumped(0),destinationIPLookedUp_(0) {
#if USE_HTTP_VIOLATIONS
nocache_hack = 0;
#endif