--- /dev/null
+#include "squid.h"
+#include "client_side.h"
+#include "client_side_request.h"
+#include "client_side_reply.h"
+#include "Downloader.h"
+#include "http/one/RequestParser.h"
+
+CBDATA_CLASS_INIT(Downloader);
+
+Downloader::Downloader(SBuf &url, const MasterXaction::Pointer &xact, AsyncCall::Pointer &aCallback):
+ AsyncJob("Downloader"),
+ ConnStateData(xact),
+ url_(url),
+ callback(aCallback),
+ status(Http::scNone)
+{
+ maxObjectSize = 512*1024;
+}
+
+Downloader::~Downloader()
+{
+ debugs(33 , 2, "Downloader Finished");
+}
+
+void
+Downloader::callException(const std::exception &e)
+{
+ debugs(33 , 2, "Downloader caught:" << e.what());
+ AsyncJob::callException(e);
+}
+
+bool
+Downloader::doneAll() const
+{
+ return (!callback || callback->canceled()) && AsyncJob::doneAll();
+}
+
+void
+Downloader::start()
+{
+ BodyProducer::start();
+ HttpControlMsgSink::start();
+ if (ClientSocketContext *context = parseOneRequest()) {
+ context->registerWithConn();
+ processParsedRequest(context);
+
+ /**/
+ if (context->flags.deferred) {
+ if (context != context->http->getConn()->getCurrentContext().getRaw())
+ context->deferRecipientForLater(context->deferredparams.node, context->deferredparams.rep, context->deferredparams.queuedBuffer);
+ else
+ context->http->getConn()->handleReply(context->deferredparams.rep, context->deferredparams.queuedBuffer);
+ }
+ /**/
+
+ }
+
+}
+
+void
+Downloader::noteMoreBodySpaceAvailable(BodyPipe::Pointer)
+{
+ // This method required only if we need to support uploading data to server
+ // Currently only GET requests are supported
+ assert(0);
+}
+
+void
+Downloader::noteBodyConsumerAborted(BodyPipe::Pointer)
+{
+ // This method required only if we need to support uploading data to server
+ // Currently only GET requests are supported
+ assert(0);
+}
+
+ClientSocketContext *
+Downloader::parseOneRequest()
+{
+ const HttpRequestMethod method = Http::METHOD_GET;
+
+ char *uri = strdup(url_.c_str());
+ HttpRequest *const request = HttpRequest::CreateFromUrlAndMethod(uri, method);
+ if (!request) {
+ debugs(33, 5, "Invalid FTP URL: " << uri);
+ safe_free(uri);
+ return NULL; //earlyError(...)
+ }
+ request->http_ver = Http::ProtocolVersion();
+ request->header.putStr(Http::HdrType::HOST, request->url.host());
+ request->header.putTime(Http::HdrType::DATE, squid_curtime);
+
+ ClientHttpRequest *const http = new ClientHttpRequest(this);
+ http->request = request;
+ HTTPMSGLOCK(http->request);
+ http->req_sz = 0;
+ http->uri = uri;
+
+ ClientSocketContext *const context = new ClientSocketContext(NULL, http);
+ StoreIOBuffer tempBuffer;
+ tempBuffer.data = context->reqbuf;
+ tempBuffer.length = HTTP_REQBUF_SZ;
+
+ ClientStreamData newServer = new clientReplyContext(http);
+ ClientStreamData newClient = context;
+ clientStreamInit(&http->client_stream, clientGetMoreData, clientReplyDetach,
+ clientReplyStatus, newServer, clientSocketRecipient,
+ clientSocketDetach, newClient, tempBuffer);
+
+ context->flags.parsed_ok = 1;
+ return context;
+}
+
+void
+Downloader::processParsedRequest(ClientSocketContext *context)
+{
+ Must(context != NULL);
+ Must(getConcurrentRequestCount() == 1);
+
+ ClientHttpRequest *const http = context->http;
+ assert(http != NULL);
+
+ debugs(33, 4, "forwarding request to server side");
+ assert(http->storeEntry() == NULL);
+ clientProcessRequest(this, Http1::RequestParserPointer(), context);
+}
+
+time_t
+Downloader::idleTimeout() const
+{
+ // No need to be implemented for connection-less ConnStateData object.
+ assert(0);
+ return 0;
+}
+
+void
+Downloader::writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call)
+{
+}
+
+void
+Downloader::handleReply(HttpReply *reply, StoreIOBuffer receivedData)
+{
+ bool existingContent = reply ? reply->content_length : 0;
+ bool exceedSize = (getCurrentContext()->startOfOutput() && existingContent > -1 && (size_t)existingContent > maxObjectSize) ||
+ ((object.length() + receivedData.length) > maxObjectSize);
+
+ if (exceedSize) {
+ status = Http::scInternalServerError;
+ callBack();
+ return;
+ }
+
+ debugs(33, 4, "Received " << receivedData.length <<
+ " object data, offset: " << receivedData.offset <<
+ " error flag:" << receivedData.flags.error);
+
+ if (receivedData.length > 0) {
+ object.append(receivedData.data, receivedData.length);
+ getCurrentContext()->http->out.size += receivedData.length;
+ getCurrentContext()->noteSentBodyBytes(receivedData.length);
+ }
+
+ switch (getCurrentContext()->socketState()) {
+ case STREAM_NONE:
+ debugs(33, 3, "Get more data");
+ getCurrentContext()->pullData();
+ break;
+ case STREAM_COMPLETE:
+ debugs(33, 3, "Object data transfer successfully complete");
+ status = Http::scOkay;
+ callBack();
+ break;
+ case STREAM_UNPLANNED_COMPLETE:
+ debugs(33, 3, "Object data transfer failed: STREAM_UNPLANNED_COMPLETE");
+ status = Http::scInternalServerError;
+ callBack();
+ break;
+ case STREAM_FAILED:
+ debugs(33, 3, "Object data transfer failed: STREAM_FAILED");
+ status = Http::scInternalServerError;
+ callBack();
+ break;
+ default:
+ fatal("unreachable code");
+ }
+}
+
+void
+Downloader::downloadFinished()
+{
+ debugs(33, 3, "fake call, to just delete the Downloader");
+
+ // Not really needed. Squid will delete this object because "doneAll" is true.
+ //deleteThis("completed");
+}
+
+void
+Downloader::callBack()
+{
+ CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer());
+ Must(dialer);
+ dialer->status = status;
+ if (status == Http::scOkay)
+ dialer->object = object;
+ ScheduleCallHere(callback);
+ callback = NULL;
+ // Calling deleteThis method here to finish Downloader
+ // may result to squid crash.
+ // This method called by handleReply method which maybe called
+ // by ClientHttpRequest::doCallouts. The doCallouts after this object deleted
+ // may operate on non valid objects.
+ // Schedule a fake call here just to force squid to delete this object
+ CallJobHere(33, 7, CbcPointer<Downloader>(this), Downloader, downloadFinished);
+}
+
+bool
+Downloader::isOpen() const
+{
+ return cbdataReferenceValid(this) && // XXX: checking "this" in a method
+ callback != NULL;
+}
--- /dev/null
+#ifndef SQUID_DOWNLOADER_H
+#define SQUID_DOWNLOADER_H
+
+#include "client_side.h"
+#include "cbdata.h"
+
+class Downloader: public ConnStateData
+{
+ CBDATA_CLASS(Downloader);
+ // XXX CBDATA_CLASS expands to nonvirtual toCbdata, AsyncJob::toCbdata
+ // is pure virtual. breaks build on clang if override is used
+
+public:
+ class CbDialer {
+ public:
+ CbDialer(): status(Http::scNone) {}
+ virtual ~CbDialer() {}
+ SBuf object;
+ Http::StatusCode status;
+ };
+
+ explicit Downloader(SBuf &url, const MasterXaction::Pointer &xact, AsyncCall::Pointer &aCallback);
+ virtual ~Downloader();
+ void downloadFinished();
+
+ /* ConnStateData API */
+ virtual bool isOpen() const;
+
+ /* AsyncJob API */
+ virtual void callException(const std::exception &e);
+ virtual bool doneAll() const;
+
+ /*Bodypipe API*/
+ virtual void noteMoreBodySpaceAvailable(BodyPipe::Pointer);
+ virtual void noteBodyConsumerAborted(BodyPipe::Pointer);
+
+protected:
+ /* ConnStateData API */
+ virtual ClientSocketContext *parseOneRequest();
+ virtual void processParsedRequest(ClientSocketContext *context);
+ virtual time_t idleTimeout() const;
+ virtual void writeControlMsgAndCall(ClientSocketContext *context, HttpReply *rep, AsyncCall::Pointer &call);
+ virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData);
+
+ /* AsyncJob API */
+ virtual void start();
+
+private:
+ void callBack();
+ SBuf url_;
+ AsyncCall::Pointer callback;
+ Http::StatusCode status;
+ SBuf object; //object data
+ size_t maxObjectSize;
+};
+
+#endif
dlink.h \
dlink.cc \
$(DNSSOURCE) \
+ Downloader.h \
+ Downloader.cc \
enums.h \
err_type.h \
err_detail_type.h \
#include "Notes.h"
#include "security/forward.h"
#include "SquidTime.h"
+#if USE_OPENSSL
+#include "ssl/support.h"
+#endif
#include "YesNoNone.h"
#if USE_OPENSSL
struct {
Security::ContextPointer sslContext;
#if USE_OPENSSL
+ char *untrustedCertsPath;
acl_access *cert_error;
sslproxy_cert_sign *cert_sign;
sslproxy_cert_adapt *cert_adapt;
Config2.effectiveGroupID = grp->gr_gid;
}
+#if USE_OPENSSL
+ if (Config.ssl_client.untrustedCertsPath)
+ Ssl::loadSquidUntrusted(Config.ssl_client.untrustedCertsPath);
+#endif
+
if (Security::ProxyOutgoingConfig.encryptTransport) {
debugs(3, DBG_IMPORTANT, "Initializing https:// proxy context");
Config.ssl_client.sslContext = Security::ProxyOutgoingConfig.createClientContext(false);
debugs(3, DBG_IMPORTANT, "ERROR: proxying https:// currently still requires --with-openssl");
#endif
}
+#if USE_OPENSSL
+ Ssl::useSquidUntrusted(Config.ssl_client.sslContext);
+#endif
}
for (CachePeer *p = Config.peers; p != NULL; p = p->next) {
free_all();
#if USE_OPENSSL
SSL_CTX_free(Config.ssl_client.sslContext);
+ Ssl::unloadSquidUntrusted();
#endif
}
Sets the cache size to use for ssl session
DOC_END
+NAME: sslproxy_untrusted_certs
+IFDEF: USE_OPENSSL
+DEFAULT: none
+LOC: Config.ssl_client.untrustedCertsPath
+TYPE: string
+DOC_START
+ Squid uses the intermediate certificates pre-loaded from the specified
+ file to validate origin server certificate chains. Squid receives many
+ incomplete chains (i.e., chains with intermediate certificates missing).
+ The file is expected to contain zero or more PEM-encoded intermediate
+ certificates. These certificates are not treated as trusted root
+ certificates.
+DOC_END
+
NAME: sslproxy_cert_sign_hash
IFDEF: USE_OPENSSL
DEFAULT: none
debugs(33, 2, HERE << clientConnection);
flags.readMore = false;
DeregisterRunner(this);
- clientdbEstablished(clientConnection->remote, -1); /* decrement */
+ if (clientConnection != NULL)
+ clientdbEstablished(clientConnection->remote, -1); /* decrement */
assert(areAllContextsForThisConnection());
freeAllContexts();
HttpReply * rep, StoreIOBuffer receivedData)
{
// dont tryt to deliver if client already ABORTED
- if (!http->getConn() || !cbdataReferenceValid(http->getConn()) || !Comm::IsConnOpen(http->getConn()->clientConnection))
+ if (!http->getConn() || !cbdataReferenceValid(http->getConn()))
return;
+ // If it is not connectionless and connection is closed return
+ if (!http->getConn()->connectionless() && !Comm::IsConnOpen(http->getConn()->clientConnection))
+ return;
+
+
/* Test preconditions */
assert(node != NULL);
PROF_start(clientSocketRecipient);
request->flags.accelerated = http->flags.accel;
request->flags.sslBumped=conn->switchedToHttps();
- request->flags.ignoreCc = conn->port->ignore_cc;
- // TODO: decouple http->flags.accel from request->flags.sslBumped
- request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
- !conn->port->allow_direct : 0;
+ if (!conn->connectionless()) {
+ request->flags.ignoreCc = conn->port->ignore_cc;
+ // TODO: decouple http->flags.accel from request->flags.sslBumped
+ request->flags.noDirect = (request->flags.accelerated && !request->flags.sslBumped) ?
+ !conn->port->allow_direct : 0;
+ }
#if USE_AUTH
if (request->flags.sslBumped) {
if (conn->getAuth() != NULL)
request->flags.internal = http->flags.internal;
setLogUri (http, urlCanonicalClean(request.getRaw()));
- request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member.
+ if (!conn->connectionless()) {
+ request->client_addr = conn->clientConnection->remote; // XXX: remove reuest->client_addr member.
#if FOLLOW_X_FORWARDED_FOR
// indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:)
// not a details about teh TCP connection itself
- request->indirect_client_addr = conn->clientConnection->remote;
+ request->indirect_client_addr = conn->clientConnection->remote;
#endif /* FOLLOW_X_FORWARDED_FOR */
- request->my_addr = conn->clientConnection->local;
- request->myportname = conn->port->name;
+ request->my_addr = conn->clientConnection->local;
+ request->myportname = conn->port->name;
+ }
if (!isFtp) {
// XXX: for non-HTTP messages instantiate a different HttpMsg child type
// store the details required for creating more MasterXaction objects as new requests come in
clientConnection = xact->tcpClient;
port = xact->squidPort;
- transferProtocol = port->transport; // default to the *_port protocol= setting. may change later.
- log_addr = xact->tcpClient->remote;
+ if (port != NULL)
+ transferProtocol = port->transport; // default to the *_port protocol= setting. may change later.
+ if (xact->tcpClient != NULL)
+ log_addr = xact->tcpClient->remote;
log_addr.applyMask(Config.Addrs.client_netmask);
// register to receive notice of Squid signal events
ClientSocketContext::Pointer getCurrentContext() const;
void addContextToQueue(ClientSocketContext * context);
int getConcurrentRequestCount() const;
- bool isOpen() const;
+ virtual bool isOpen() const;
/// Update flags and timeout after the first byte received
void receivedFirstByte();
/// Squid listening port details where this connection arrived.
AnyP::PortCfgPointer port;
+ /// If the port is not set then it is a connection-less object
+ /// created by an internal squid subsystem
+ bool connectionless() const { return port == NULL; }
bool transparent() const;
bool reading() const;
void stopReading(); ///< cancels comm_read if it is scheduled
if (EBIT_TEST(http->storeEntry()->flags, ENTRY_SPECIAL)) {
hdr->delById(Http::HdrType::DATE);
hdr->putTime(Http::HdrType::DATE, squid_curtime);
- } else if (http->getConn() && http->getConn()->port->actAsOrigin) {
+ } else if (http->getConn() && !http->getConn()->connectionless() && http->getConn()->port->actAsOrigin) {
// Swap the Date: header to current time if we are simulating an origin
HttpHeaderEntry *h = hdr->findEntry(Http::HdrType::DATE);
if (h)
request->flags.proxyKeepalive = false;
} else if (http->getConn()) {
ConnStateData * conn = http->getConn();
- if (!Comm::IsConnOpen(conn->port->listenConn)) {
+ if (conn->connectionless()) {
+ debugs(88, 3, "connection-less object, close after finished");
+ request->flags.proxyKeepalive = false;
+ } else if (!Comm::IsConnOpen(conn->port->listenConn)) {
// The listening port closed because of a reconfigure
debugs(88, 3, "listening port closed");
request->flags.proxyKeepalive = false;
if (!http_conn)
return;
+ // Internal requests such as those from Doenloader does not have local port
+ if (http_conn->port == NULL)
+ return;
+
request->flags.connectionAuthDisabled = http_conn->port->connection_auth_disabled;
if (!request->flags.connectionAuthDisabled) {
if (Comm::IsConnOpen(http_conn->pinning.serverConnection)) {
#include "CachePeer.h"
#include "client_side.h"
#include "comm/Loops.h"
+#include "Downloader.h"
#include "errorpage.h"
#include "fde.h"
#include "globals.h"
} else if (finalAction != Ssl::bumpSplice) {
//Allow write, proceed with the connection
srvBio->holdWrite(false);
- srvBio->recordInput(false);
debugs(83,5, "Retry the fwdNegotiateSSL on FD " << serverConn->fd);
Ssl::PeerConnector::noteWantWrite();
} else {
void
Ssl::PeerConnector::noteWantRead()
{
- setReadTimeout();
const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ if (srvBio->holdRead()) {
+ if (srvBio->gotHello()) {
+ if (checkForMissingCertificates())
+ return; // Wait to download certificates before proceed.
+
+ srvBio->holdRead(false);
+ // Schedule a negotiateSSl to allow openSSL parse received data
+ Ssl::PeerConnector::NegotiateSsl(fd, this);
+ return;
+ } else if (srvBio->gotHelloFailed()) {
+ srvBio->holdRead(false);
+ debugs(83, DBG_IMPORTANT, "Error parsing SSL Server Hello Message on FD " << fd);
+ // Schedule a negotiateSSl to allow openSSL parse received data
+ Ssl::PeerConnector::NegotiateSsl(fd, this);
+ return;
+ }
+ }
+
+ setReadTimeout();
Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
}
return buf.content();
}
+class PeerConnectorCertDownloaderDialer: public CallDialer, public Downloader::CbDialer
+{
+public:
+ typedef void (Ssl::PeerConnector::*Method)(SBuf &object, int status);
+
+ PeerConnectorCertDownloaderDialer(Method method, Ssl::PeerConnector *pc):
+ method_(method),
+ peerConnector_(pc) {}
+
+ /* CallDialer API */
+ virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); }
+ void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); }
+ virtual void print(std::ostream &os) const {
+ os << '(' << peerConnector_.get() << ", Http Status:" << status << ')';
+ }
+
+ Method method_;
+ CbcPointer<Ssl::PeerConnector> peerConnector_;
+};
+
+void
+Ssl::PeerConnector::startCertDownloading(SBuf &url)
+{
+ AsyncCall::Pointer certCallback = asyncCall(81, 4,
+ "Ssl::PeerConnector::certDownloadingDone",
+ PeerConnectorCertDownloaderDialer(&Ssl::PeerConnector::certDownloadingDone, this));
+
+ MasterXaction *xaction = new MasterXaction;
+ Downloader *dl = new Downloader(url, xaction, certCallback);
+ AsyncJob::Start(dl);
+}
+
+void
+Ssl::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus)
+{
+ debugs(81, 5, "OK! certificate downloaded, status: " << downloadStatus << " data size: " << obj.length());
+
+ // Get ServerBio from SSL object
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+
+ // Parse Certificate. Assume that it is in DER format. Probably we should handle PEM or other formats too
+ const unsigned char *raw = (const unsigned char*)obj.rawContent();
+ if (X509 *cert = d2i_X509(NULL, &raw, obj.length())) {
+ char buffer[1024];
+ debugs(81, 5, "Retrieved certificate: " << X509_NAME_oneline(X509_get_subject_name(cert), buffer, 1024));
+ const Ssl::X509_STACK_Pointer &certsList = srvBio->serverCertificates();
+ if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList)) {
+ urlsOfMissingCerts.push(SBuf(issuerUri));
+ }
+ Ssl::SSL_add_untrusted_cert(ssl, cert);
+ }
+
+ // Check if has uri to donwload and add it to urlsOfMissingCerts
+ if (urlsOfMissingCerts.size()) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return;
+ }
+
+ srvBio->holdRead(false);
+ Ssl::PeerConnector::NegotiateSsl(serverConnection()->fd, this);
+}
+
+bool
+Ssl::PeerConnector::checkForMissingCertificates ()
+{
+ const int fd = serverConnection()->fd;
+ SSL *ssl = fd_table[fd].ssl;
+ BIO *b = SSL_get_rbio(ssl);
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
+ const Ssl::X509_STACK_Pointer &certs = srvBio->serverCertificates();
+
+ if (sk_X509_num(certs.get())) {
+ debugs(83, 5, "SSL server sent " << sk_X509_num(certs.get()) << " certificates");
+ Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs);
+ if (urlsOfMissingCerts.size()) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return true;
+ }
+ }
+
+ return false;
+}
+
SSL_CTX *
Ssl::BlindPeerConnector::getSslContext()
{
Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(b->ptr);
// Inherite client features, like SSL version, SNI and other
srvBio->setClientFeatures(features);
- srvBio->recordInput(true);
srvBio->mode(csd->sslBumpMode);
}
}
#include "security/EncryptorAnswer.h"
#include "ssl/support.h"
#include <iosfwd>
+#include <queue>
class HttpRequest;
class ErrorState;
/// Squid COMM_SELECT_READ handler.
void noteWantRead();
+ bool checkForMissingCertificates();
+
+ void startCertDownloading(SBuf &url);
+
+ void certDownloadingDone(SBuf &object, int status);
+
/// Called when the openSSL SSL_connect function needs to write data to
/// the remote SSL server. Sets the Squid COMM_SELECT_WRITE handler.
virtual void noteWantWrite();
time_t negotiationTimeout; ///< the SSL connection timeout to use
time_t startTime; ///< when the peer connector negotiation started
bool useCertValidator_; ///< whether the certificate validator should bypassed
+
+ std::queue<SBuf> urlsOfMissingCerts;
};
/// A simple PeerConnector for SSL/TLS cache_peers. No SslBump capabilities.
}
int
-Ssl::Bio::readAndBuffer(char *buf, int size, BIO *table, const char *description)
+Ssl::Bio::readAndBuffer(BIO *table, const char *description)
{
prepReadBuf();
-
- size = min((int)rbuf.potentialSpaceSize(), size);
+ char buf[SQUID_TCP_SO_RCVBUF ];
+ size_t size = min(rbuf.potentialSpaceSize(), (mb_size_t)SQUID_TCP_SO_RCVBUF);
if (size <= 0) {
debugs(83, DBG_IMPORTANT, "Not enough space to hold " <<
rbuf.contentSize() << "+ byte " << description);
return -1;
}
- const int bytes = Ssl::Bio::read(buf, size, table);
+ const int bytes = Ssl::Bio::read(buf, sizeof(buf), table);
debugs(83, 5, "read " << bytes << " out of " << size << " bytes"); // move to Ssl::Bio::read()
if (bytes > 0) {
Ssl::ClientBio::read(char *buf, int size, BIO *table)
{
if (helloState < atHelloReceived) {
- int bytes = readAndBuffer(buf, size, table, "TLS client Hello");
+ int bytes = readAndBuffer(table, "TLS client Hello");
if (bytes <= 0)
return bytes;
}
clientFeatures = features;
};
+int
+Ssl::ServerBio::readAndBufferServerHelloMsg(BIO *table, const char *description)
+{
+
+ int ret = readAndBuffer(table, description);
+ if (ret <= 0)
+ return ret;
+
+ if (!parser_.parseServerHello((const unsigned char *)rbuf.content(), rbuf.contentSize())) {
+ if (!parser_.parseError)
+ BIO_set_retry_read(table);
+ return -1;
+ }
+
+ return 1;
+}
+
int
Ssl::ServerBio::read(char *buf, int size, BIO *table)
{
- return record_ ?
- readAndBuffer(buf, size, table, "TLS server Hello") : Ssl::Bio::read(buf, size, table);
+ if (parser_.state < Ssl::HandshakeParser::atHelloDoneReceived) {
+ int ret = readAndBufferServerHelloMsg(table, "TLS server Hello");
+ if (ret <= 0)
+ return ret;
+ }
+
+ if (holdRead_) {
+ debugs(83, 7, "Hold flag is set on ServerBio, retry latter. (Hold " << size << "bytes)");
+ BIO_set_retry_read(table);
+ return -1;
+ }
+
+ if (parser_.parseDone && !parser_.parseError) {
+ int unsent = rbuf.contentSize() - rbufConsumePos;
+ if (unsent > 0) {
+ int bytes = (size <= unsent ? size : unsent);
+ memcpy(buf, rbuf.content()+rbufConsumePos, bytes);
+ rbufConsumePos += bytes;
+ debugs(83, 7, "Pass " << bytes << " bytes to openSSL as read");
+ return bytes;
+ } else
+ return Ssl::Bio::read(buf, size, table);
+ }
+
+ return -1;
}
// This function makes the required checks to examine if the client hello
bool
Ssl::ServerBio::resumingSession()
{
- if (!serverFeatures.initialized_)
- serverFeatures.get(rbuf, false);
-
- if (!clientFeatures.sessionId.isEmpty() && !serverFeatures.sessionId.isEmpty())
- return clientFeatures.sessionId == serverFeatures.sessionId;
+ return parser_.ressumingSession;
+}
- // is this a session resuming attempt using TLS tickets?
- if (clientFeatures.hasTlsTicket &&
- serverFeatures.tlsTicketsExtension &&
- serverFeatures.hasCcsOrNst)
- return true;
+const Ssl::X509_STACK_Pointer &
+Ssl::ServerBio::serverCertificates()
+{
+ if (!serverCertificates_.get()) {
+ serverCertificates_.reset(sk_X509_new_null());
+ parser_.parseServerCertificates(serverCertificates_, (const unsigned char *)rbuf.content(), rbuf.contentSize());
+ }
- return false;
+ return serverCertificates_;
}
/// initializes BIO table after allocation
}
}
-Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1), helloMsgSize(0), unknownCiphers(false), doHeartBeats(true), tlsTicketsExtension(false), hasTlsTicket(false), tlsStatusRequest(false), hasCcsOrNst(false), initialized_(false)
+Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1), helloMsgSize(0), unknownCiphers(false), doHeartBeats(true), tlsTicketsExtension(false), hasTlsTicket(false), tlsStatusRequest(false), initialized_(false)
{
memset(client_random, 0, SSL3_RANDOM_SIZE);
}
return helloMsgSize;
}
-bool
-Ssl::Bio::sslFeatures::checkForCcsOrNst(const unsigned char *msg, size_t size)
-{
- while (size > 5) {
- const int msgType = msg[0];
- const int msgSslVersion = (msg[1] << 8) | msg[2];
- debugs(83, 7, "SSL Message Version :" << std::hex << std::setw(8) << std::setfill('0') << msgSslVersion);
- // Check for Change Cipher Spec message
- // RFC5246 section 6.2.1
- if (msgType == 0x14) {// Change Cipher Spec message found
- debugs(83, 7, "SSL Change Cipher Spec message found");
- return true;
- }
- // Check for New Session Ticket message
- // RFC5077 section 3.3
- if (msgType == 0x04) {// New Session Ticket message found
- debugs(83, 7, "TLS New Session Ticket message found");
- return true;
- }
- // The hello message size exist in 4th and 5th bytes
- size_t msgLength = (msg[3] << 8) + msg[4];
- debugs(83, 7, "SSL Message Size: " << msgLength);
- msgLength += 5;
-
- if (msgLength <= size) {
- msg += msgLength;
- size -= msgLength;
- } else
- size = 0;
- }
- return false;
-}
-
bool
Ssl::Bio::sslFeatures::get(const MemBuf &buf, bool record)
{
// The type 2 is a ServerHello, the type 1 is a ClientHello
// RFC5246 section 7.4
if (msg[5] == 0x2) { // ServerHello message
- if (parseV3ServerHello(msg, (size_t)msgSize)) {
- hasCcsOrNst = checkForCcsOrNst(msg + msgSize, buf.contentSize() - msgSize);
- return true;
- }
+ return parseV3ServerHello(msg, (size_t)msgSize);
} else if (msg[5] == 0x1) // ClientHello message,
return parseV3Hello(msg, (size_t)msgSize);
}
" opaquePrf:" << opaquePrf;
}
+bool
+Ssl::HandshakeParser::parseNextContentRecord(const unsigned char *msg, size_t size)
+{
+ if (unParsedContent)
+ return true;
+
+ if (parsingPos >= size)
+ return false;
+
+ msg += parsingPos;
+ size -= parsingPos;
+
+ if (size < 5)
+ return false;
+
+ const unsigned int contentType = msg[0];
+ // The hello message size exist in 4th and 5th bytes
+ size_t contentLength = (msg[3] << 8) + msg[4];
+ if (contentLength > size - 5)
+ return false; //missing message data?
+
+ unParsedContent = contentLength;
+ currentContentType = (ContentType)contentType;
+ parsingPos += 5;
+
+ currentMsg = 0;
+ currentMsgSize = 0;
+ return true;
+}
+
+bool
+Ssl::HandshakeParser::skipContentDataRecord(const unsigned char *msg, size_t size)
+{
+ if (size < parsingPos) {
+ parseDone = true;
+ parseError = true;
+ return false;
+ }
+ parsingPos += unParsedContent;
+ unParsedContent = 0;
+ currentContentType = ctNone;
+
+ currentMsg = 0;
+ currentMsgSize = 0;
+
+ return true;
+}
+
+Ssl::HandshakeParser::HandshakeType
+Ssl::HandshakeParser::parseNextHandshakeMessage(const unsigned char *msg, size_t size)
+{
+ if (!unParsedContent) {
+ return hskNone; // No data to parse
+ }
+
+ if (currentContentType != ctHandshake) {
+ parseError = true;
+ parseDone = true;
+ return hskNone;
+ }
+
+ msg += parsingPos;
+ size -= parsingPos;
+
+ const HandshakeType type = (HandshakeType)msg[0];
+ size_t msgLength = (msg[1] << 16) | (msg[2] << 8) | msg[3];
+
+ if (msgLength > size + 4 ||
+ msgLength > unParsedContent + 4) {
+ // The parseNextContentData call assure that we have all handshake data
+ // before parse this handshake message. So this is looks like a
+ // parse error
+ parseError = true;
+ parseDone = true;
+ return hskNone;
+ }
+
+ currentMsg = parsingPos + 4;
+ currentMsgSize = msgLength;
+
+ unParsedContent -= msgLength + 4;
+ parsingPos += msgLength + 4;
+
+ return type;
+}
+
+bool
+Ssl::HandshakeParser::parseServerHello(const unsigned char *data, size_t dataSize)
+{
+ while(!parseDone && !parseError) {
+ switch(state) {
+ case atHelloNone:
+ case atHelloStarted:
+ if (!parseNextContentRecord(data, dataSize))
+ return false;
+ {
+ HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+ if (type == hskNone)
+ return false; //probably the message does not received yet
+ if (type != hskServerHello) { // parse error; Expecting Server hello
+ parseError = true;
+ parseDone = true;
+ return false;
+ }
+ }
+ state = atHelloReceived;
+ break;
+
+ case atHelloReceived:
+ if (!parseNextContentRecord(data, dataSize))
+ return false;
+
+ if (currentContentType == ctChangeCipherSpec) {
+ state = atCcsReceived;
+ skipContentDataRecord(data, dataSize); // Skipe to next Record
+ } else {
+ HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+ if (type == hskNone)
+ return false; // probably an error;
+ else if (type == hskCertificate) {
+ state = atCertificatesReceived;
+ certificatesMsgPos = currentMsg;
+ certificatesMsgSize = currentMsgSize;
+ } else if (type == shkNewSessionTicket)
+ state = atNstReceived;
+ }
+ break;
+
+ case atNstReceived:
+ if (!parseNextContentRecord(data, dataSize))
+ return false;
+
+ if (currentContentType == ctChangeCipherSpec)
+ state = atCcsReceived;
+ skipContentDataRecord(data, dataSize); // Skipe to next Record
+ break;
+
+ case atCertificatesReceived: {
+ if (!parseNextContentRecord(data, dataSize))
+ return false;
+
+ HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+ if (type == hskNone)
+ return false;
+ if (type == hskServerHelloDone)
+ state = atHelloDoneReceived;
+ }
+ break;
+
+ case atCcsReceived: {
+ if (!parseNextContentRecord(data, dataSize))
+ return false;
+
+ ressumingSession = true;
+
+ // To parse a shkFinished handshake message:
+ // HandshakeType type = parseNextHandshakeMessage(data, dataSize);
+ // if (type == hskNone)
+ // return false;
+ // if (type == shkFinished)
+ // helloState = Ssl::Bio::atFinishReceived;
+ //
+ // However this message may arrived encrypted.
+ // We are accepting any handshake message for now.
+ if (currentContentType == ctHandshake && unParsedContent != 0)
+ state = atFinishReceived;
+ }
+ break;
+ case atHelloDoneReceived:
+ case atFinishReceived:
+ return (parseDone = true);
+ break;
+ }
+ }
+
+ return parseDone;
+}
+
+static X509 *
+extractCertificate(const unsigned char *currentpos, size_t size, const unsigned char **next, size_t *nextSize)
+{
+ size_t certLen = currentpos[0] << 16 | currentpos[1] << 8 | currentpos[2];
+ if (certLen + 3 > size)
+ return NULL;
+ *next = currentpos + 3 + certLen;
+ *nextSize = size - certLen - 3;
+
+ const unsigned char *raw = (const unsigned char*)(currentpos + 3);
+ return d2i_X509(NULL, &(raw), certLen);
+}
+
+bool
+Ssl::HandshakeParser::parseServerCertificates(Ssl::X509_STACK_Pointer &serverCertificates, const unsigned char *msg, size_t size)
+{
+ if (!serverCertificates.get())
+ return false; // No struct to store it? should we assert?
+
+ if (!certificatesMsgPos)
+ return false; // There is no certificates message
+
+ // We are checking for this while parsing handshake messages
+ assert(certificatesMsgPos <= size - 3);
+
+ const unsigned char *certMsg = msg + certificatesMsgPos;
+ // First 3 bytes are the size of certificates
+ size_t certsLength = ((certMsg[0] << 16) | (certMsg[1] << 8) | certMsg[2]);
+ if (certsLength > size - certificatesMsgPos) {
+ debugs(83, 2, "Error parsing certificates Handshake Message. Parsed certificates length: " << certsLength <<
+ " server hello length: " << size);
+ return false;
+ }
+ certMsg += 3; // Point to raw certificates data, in the form [certLength cert]*
+
+ const unsigned char *next = certMsg;
+ size_t nextLen = certsLength;
+ while(nextLen) {
+ X509 *cert = NULL;
+ if (!(cert = extractCertificate(next, nextLen, &next, &nextLen)))
+ break;
+ sk_X509_push(serverCertificates.get(), cert);
+ }
+ return true;
+}
+
#endif /* USE_SSL */
namespace Ssl
{
+class HandshakeParser {
+public:
+ /// The parsing states
+ typedef enum {atHelloNone = 0, atHelloStarted, atHelloReceived, atCertificatesReceived, atHelloDoneReceived, atNstReceived, atCcsReceived, atFinishReceived} ParserState;
+
+ /// TLS record protocol, content types, RFC5246 section 6.2.1
+ typedef enum {ctNone = 0, ctChangeCipherSpec = 20, ctAlert = 21, ctHandshake = 22, ctApplicationData} ContentType;
+ /// TLS Handshake protocol, handshake types, RFC5246 section 7.4
+ typedef enum {hskNone = 0, hskServerHello = 2, shkNewSessionTicket = 4, hskCertificate = 11, hskServerHelloDone = 14, hskFinished = 20} HandshakeType;
+
+ HandshakeParser(): state(atHelloNone), currentContentType(ctNone), unParsedContent(0), parsingPos(0), currentMsg(0), currentMsgSize(0), certificatesMsgPos(0), certificatesMsgSize(0), ressumingSession(false), parseDone(false), parseError(false) {}
+
+ /// Parses the SSL Server Hello records stored in data.
+ /// Return false if the hello messages are not complete (HelloDone
+ /// or Finished handshake messages are not received)
+ /// On parse error, return false and sets the parseError member to true.
+ bool parseServerHello(const unsigned char *data, size_t dataSize);
+
+ /// Parse server certificates message and store the certificate to serverCertificates list
+ bool parseServerCertificates(Ssl::X509_STACK_Pointer &serverCertificates, const unsigned char *msg, size_t size);
+
+ ParserState state; ///< current parsing state.
+
+ ContentType currentContentType; ///< The current SSL record content type
+ size_t unParsedContent; ///< The size of current SSL record, which is not parsed yet
+ size_t parsingPos; ///< The parsing position from the beginning of parsed data
+ size_t currentMsg; ///< The current handshake message possition from the beginning of parsed data
+ size_t currentMsgSize; ///< The current handshake message size.
+
+ size_t certificatesMsgPos; ///< The possition of certificates message from the beggining of parsed data
+ size_t certificatesMsgSize; ///< The size of certificates message
+ bool ressumingSession; ///< True if this is a resumming session
+
+ bool parseDone; ///< The parser finishes its job
+ bool parseError; ///< Set to tru by parse on parse error.
+
+private:
+ /// Do nothing if there are unparsed data from existing SSL record
+ /// else parses the next SSL record.
+ /// Return false if the next SSL record is not complete.
+ bool parseNextContentRecord(const unsigned char *msg, size_t size);
+ /// Consumes the current SSL record and set the parsingPos to the next
+ bool skipContentDataRecord(const unsigned char *msg, size_t size);
+ /// Parses the next handshake message in current SSL record
+ HandshakeType parseNextHandshakeMessage(const unsigned char *msg, size_t size);
+};
/// BIO source and sink node, handling socket I/O and monitoring SSL state
class Bio
/// \retval 0 if the contents of the buffer are not enough
/// \retval <0 if the contents of buf are not SSLv3 or TLS hello message
int parseMsgHead(const MemBuf &);
- /// Parses msg buffer and return true if one of the Change Cipher Spec
- /// or New Session Ticket messages found
- bool checkForCcsOrNst(const unsigned char *msg, size_t size);
public:
int sslVersion; ///< The requested/used SSL version
int compressMethod; ///< The requested/used compressed method
bool hasTlsTicket; ///< whether a TLS ticket is included
bool tlsStatusRequest; ///< whether the TLS status request extension is set
SBuf tlsAppLayerProtoNeg; ///< The value of the TLS application layer protocol extension if it is enabled
- /// whether Change Cipher Spec message included in ServerHello
- /// handshake message
- bool hasCcsOrNst;
/// The client random number
unsigned char client_random[SSL3_RANDOM_SIZE];
SBuf sessionId;
void prepReadBuf();
/// Reads data from socket and record them to a buffer
- int readAndBuffer(char *buf, int size, BIO *table, const char *description);
+ int readAndBuffer(BIO *table, const char *description);
const MemBuf &rBufData() {return rbuf;}
protected:
class ServerBio: public Bio
{
public:
- explicit ServerBio(const int anFd): Bio(anFd), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), record_(false), bumpMode_(bumpNone) {}
+ explicit ServerBio(const int anFd): Bio(anFd), helloMsgSize(0), helloBuild(false), allowSplice(false), allowBump(false), holdWrite_(false), holdRead_(true), bumpMode_(bumpNone), rbufConsumePos(0) {}
/// The ServerBio version of the Ssl::Bio::stateChanged method
virtual void stateChanged(const SSL *ssl, int where, int ret);
/// The ServerBio version of the Ssl::Bio::write method
void setClientFeatures(const sslFeatures &features);
bool resumingSession();
+
+ /// Reads Server hello message+certificates+ServerHelloDone message sent
+ /// by server and buffer it to rbuf member
+ int readAndBufferServerHelloMsg(BIO *table, const char *description);
+
/// The write hold state
bool holdWrite() const {return holdWrite_;}
/// Enables or disables the write hold state
void holdWrite(bool h) {holdWrite_ = h;}
- /// Enables or disables the input data recording, for internal analysis.
- void recordInput(bool r) {record_ = r;}
+ /// The read hold state
+ bool holdRead() const {return holdRead_;}
+ /// Enables or disables the read hold state
+ void holdRead(bool h) {holdRead_ = h;}
/// Whether we can splice or not the SSL stream
bool canSplice() {return allowSplice;}
/// Whether we can bump or not the SSL stream
/// The bumping mode
void mode(Ssl::BumpMode m) {bumpMode_ = m;}
Ssl::BumpMode bumpMode() {return bumpMode_;} ///< return the bumping mode
+
+ /// Return true if the Server hello message received
+ bool gotHello() const { return (parser_.parseDone && !parser_.parseError); }
+
+ /// Return true if the Server Hello parsing failed
+ bool gotHelloFailed() const { return (parser_.parseDone && parser_.parseError); }
+
+ const Ssl::X509_STACK_Pointer &serverCertificates();
private:
sslFeatures clientFeatures; ///< SSL client features extracted from ClientHello message or SSL object
- sslFeatures serverFeatures; ///< SSL server features extracted from ServerHello message
SBuf helloMsg; ///< Used to buffer output data.
mb_size_t helloMsgSize;
bool helloBuild; ///< True if the client hello message sent to the server
bool allowSplice; ///< True if the SSL stream can be spliced
bool allowBump; ///< True if the SSL stream can be bumped
bool holdWrite_; ///< The write hold state of the bio.
- bool record_; ///< If true the input data recorded to rbuf for internal use
+ bool holdRead_; ///< The read hold state of the bio.
Ssl::BumpMode bumpMode_;
+
+ ///< The size of data stored in rbuf which passed to the openSSL
+ size_t rbufConsumePos;
+ HandshakeParser parser_; ///< The SSL messages parser.
+ Ssl::X509_STACK_Pointer serverCertificates_; ///< The certificates chain sent by the SSL server
};
inline
#include <cerrno>
+// TODO: Move ssl_ex_index_* global variables from global.cc here.
+int ssl_ex_index_ssl_untrusted_chain = -1;
+
static void setSessionCallbacks(SSL_CTX *ctx);
Ipc::MemMap *SslSessionCache = NULL;
const char *SslSessionCacheName = "ssl_session_cache";
+static Ssl::CertsIndexedList SquidUntrustedCerts;
+
const EVP_MD *Ssl::DefaultSignHash = NULL;
const char *Ssl::BumpModeStr[] = {
ssl_ex_index_ssl_errors = SSL_get_ex_new_index(0, (void *) "ssl_errors", NULL, NULL, &ssl_free_SslErrors);
ssl_ex_index_ssl_cert_chain = SSL_get_ex_new_index(0, (void *) "ssl_cert_chain", NULL, NULL, &ssl_free_CertChain);
ssl_ex_index_ssl_validation_counter = SSL_get_ex_new_index(0, (void *) "ssl_validation_counter", NULL, NULL, &ssl_free_int);
+ ssl_ex_index_ssl_untrusted_chain = SSL_get_ex_new_index(0, (void *) "ssl_untrusted_chain", NULL, NULL, &ssl_free_CertChain);
+}
+
+bool
+Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
+{
+ BIO *in = BIO_new_file(certsFile, "r");
+ if (!in) {
+ debugs(83, DBG_IMPORTANT, "Failed to open '" << certsFile << "' to load certificates");
+ return false;
+ }
+
+ X509 *aCert;
+ while((aCert = PEM_read_bio_X509(in, NULL, NULL, NULL))) {
+ static char buffer[2048];
+ X509_NAME_oneline(X509_get_subject_name(aCert), buffer, sizeof(buffer));
+ list.insert(std::pair<SBuf, X509 *>(SBuf(buffer), aCert));
+ }
+ debugs(83, 4, "Loaded " << list.size() << " certificates from file: '" << certsFile << "'");
+ BIO_free(in);
+ return true;
}
#if defined(SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS)
}
}
+static const char *
+hasAuthorityInfoAccessCaIssuers(X509 *cert)
+{
+ AUTHORITY_INFO_ACCESS *info;
+ if (!cert)
+ return NULL;
+ info = (AUTHORITY_INFO_ACCESS *)X509_get_ext_d2i(cert, NID_info_access, NULL, NULL);
+ if (!info)
+ return NULL;
+
+ static char uri[MAX_URL];
+ uri[0] = '\0';
+
+ for (int i = 0; i < sk_ACCESS_DESCRIPTION_num(info); i++) {
+ ACCESS_DESCRIPTION *ad = sk_ACCESS_DESCRIPTION_value(info, i);
+ if (OBJ_obj2nid(ad->method) == NID_ad_ca_issuers) {
+ if (ad->location->type == GEN_URI) {
+ xstrncpy(uri, (char *)ASN1_STRING_data(ad->location->d.uniformResourceIdentifier), sizeof(uri));
+ }
+ break;
+ }
+ }
+ AUTHORITY_INFO_ACCESS_free(info);
+ return uri[0] != '\0' ? uri : NULL;
+}
+
+/// quickly find a certificate with a given issuer in Ssl::CertsIndexedList.
+static X509 *
+findCertByIssuerFast(Ssl::CertsIndexedList &list, X509 *cert)
+{
+ static char buffer[2048];
+
+ if (X509_NAME *issuerName = X509_get_issuer_name(cert))
+ X509_NAME_oneline(issuerName, buffer, sizeof(buffer));
+ else
+ return NULL;
+
+ const auto ret = list.equal_range(SBuf(buffer));
+ for (Ssl::CertsIndexedList::iterator it = ret.first; it != ret.second; ++it) {
+ X509 *issuer = it->second;
+ if (X509_check_issued(cert, issuer)) {
+ return issuer;
+ }
+ }
+ return NULL;
+}
+
+/// slowly find a certificate with a given issuer using linear search
+static X509 *
+findCertByIssuerSlowly(STACK_OF(X509) *sk, X509 *cert)
+{
+ if (!sk)
+ return NULL;
+
+ const int skItemsNum = sk_X509_num(sk);
+ for (int i = 0; i < skItemsNum; ++i) {
+ X509 *issuer = sk_X509_value(sk, i);
+ if (X509_check_issued(cert, issuer) == X509_V_OK)
+ return issuer;
+ }
+ return NULL;
+}
+
+const char *
+Ssl::uriOfIssuerIfMissing(X509 *cert, Ssl::X509_STACK_Pointer const &serverCertificates)
+{
+ if (!cert || !serverCertificates.get())
+ return NULL;
+
+ if (!findCertByIssuerSlowly(serverCertificates.get(), cert)) {
+ //if issuer is missing ...
+ if (!findCertByIssuerFast(SquidUntrustedCerts, cert)) {
+ // and issuer not found in local untrusted certificates database
+ if (const char *issuerUri = hasAuthorityInfoAccessCaIssuers(cert)) {
+ // There is a URI where we can download a certificate.
+ // Check to see if this is required
+ return issuerUri;
+ }
+ }
+ }
+ return NULL;
+}
+
+void
+Ssl::missingChainCertificatesUrls(std::queue<SBuf> &URIs, Ssl::X509_STACK_Pointer const &serverCertificates)
+{
+ if (!serverCertificates.get())
+ return;
+
+ for (int i = 0; i < sk_X509_num(serverCertificates.get()); ++i) {
+ X509 *cert = sk_X509_value(serverCertificates.get(), i);
+ if (const char *issuerUri = uriOfIssuerIfMissing(cert, serverCertificates))
+ URIs.push(SBuf(issuerUri));
+ }
+}
+
+void
+Ssl::SSL_add_untrusted_cert(SSL *ssl, X509 *cert)
+{
+ STACK_OF(X509) *untrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
+ if (!untrustedStack) {
+ untrustedStack = sk_X509_new_null();
+ if (!SSL_set_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain, untrustedStack)) {
+ sk_X509_pop_free(untrustedStack, X509_free); //?? print an error?
+ return;
+ }
+ }
+ sk_X509_push(untrustedStack, cert);
+}
+
+/// add missing issuer certificates to untrustedCerts
+static void
+completeIssuers(X509_STORE_CTX *ctx, STACK_OF(X509) *untrustedCerts)
+{
+ debugs(83, 2, "completing " << sk_X509_num(untrustedCerts) << " OpenSSL untrusted certs using " << SquidUntrustedCerts.size() << " configured untrusted certificates");
+
+ int depth = ctx->param->depth;
+ X509 *current = ctx->cert;
+ int i = 0;
+ for (i = 0; current && (i < depth); ++i) {
+ if (X509_check_issued(current, current)) {
+ // either ctx->cert is itself self-signed or untrustedCerts
+ // aready contain the self-signed current certificate
+ break;
+ }
+
+ // untrustedCerts is short, not worth indexing
+ X509 *issuer = findCertByIssuerSlowly(untrustedCerts, current);
+ if (!issuer) {
+ if ((issuer = findCertByIssuerFast(SquidUntrustedCerts, current)))
+ sk_X509_push(untrustedCerts, issuer);
+ }
+ current = issuer;
+ }
+
+ if (i >= depth)
+ debugs(83, 2, "exceeded the maximum certificate chain length: " << depth);
+}
+
+/// OpenSSL certificate validation callback.
+static int
+untrustedToStoreCtx_cb(X509_STORE_CTX *ctx,void *data)
+{
+ debugs(83, 4, "Try to use pre-downloaded intermediate certificates\n");
+
+ SSL *ssl = (SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
+ STACK_OF(X509) *sslUntrustedStack = static_cast <STACK_OF(X509) *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_untrusted_chain));
+
+ // OpenSSL already maintains ctx->untrusted but we cannot modify
+ // internal OpenSSL list directly. We have to give OpenSSL our own
+ // list, but it must include certificates on the OpenSSL ctx->untrusted
+ STACK_OF(X509) *oldUntrusted = ctx->untrusted;
+ STACK_OF(X509) *sk = sk_X509_dup(oldUntrusted); // oldUntrusted is always not NULL
+
+ for (int i = 0; i < sk_X509_num(sslUntrustedStack); ++i) {
+ X509 *cert = sk_X509_value(sslUntrustedStack, i);
+ sk_X509_push(sk, cert);
+ }
+
+ // If the local untrusted certificates internal database is used
+ // run completeIssuers to add missing certificates if possible.
+ if (SquidUntrustedCerts.size() > 0)
+ completeIssuers(ctx, sk);
+
+ X509_STORE_CTX_set_chain(ctx, sk); // No locking/unlocking, just sets ctx->untrusted
+ int ret = X509_verify_cert(ctx);
+ X509_STORE_CTX_set_chain(ctx, oldUntrusted); // Set back the old untrusted list
+ sk_X509_free(sk); // Release sk list
+ return ret;
+}
+
+void
+Ssl::useSquidUntrusted(SSL_CTX *sslContext)
+{
+ SSL_CTX_set_cert_verify_callback(sslContext, untrustedToStoreCtx_cb, NULL);
+}
+
+bool
+Ssl::loadSquidUntrusted(const char *path)
+{
+ return Ssl::loadCerts(path, SquidUntrustedCerts);
+}
+
+void
+Ssl::unloadSquidUntrusted()
+{
+ if (SquidUntrustedCerts.size()) {
+ for (Ssl::CertsIndexedList::iterator it = SquidUntrustedCerts.begin(); it != SquidUntrustedCerts.end(); ++it) {
+ X509_free(it->second);
+ }
+ SquidUntrustedCerts.clear();
+ }
+}
+
/**
\ingroup ServerProtocolSSLInternal
* Read certificate from file.
#define SQUID_SSL_SUPPORT_H
#include "base/CbDataList.h"
+#include "SBuf.h"
#include "security/forward.h"
#include "ssl/gadgets.h"
#if HAVE_OPENSSL_ENGINE_H
#include <openssl/engine.h>
#endif
+#include <queue>
+#include <map>
+
/**
\defgroup ServerProtocolSSLAPI Server-Side SSL API
return (0 <= bm && bm < Ssl::bumpEnd) ? Ssl::BumpModeStr[bm] : NULL;
}
+/// certificates indexed by issuer name
+typedef std::multimap<SBuf, X509 *> CertsIndexedList;
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Load PEM-encoded certificates from the given file.
+ */
+bool loadCerts(const char *certsFile, Ssl::CertsIndexedList &list);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Load PEM-encoded certificates to the squid untrusteds certificates
+ * internal DB from the given file.
+ */
+bool loadSquidUntrusted(const char *path);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Removes all certificates from squid untrusteds certificates
+ * internal DB and frees all memory
+ */
+void unloadSquidUntrusted();
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Add the certificate cert to ssl object untrusted certificates.
+ * Squid uses an attached to SSL object list of untrusted certificates,
+ * with certificates which can be used to complete incomplete chains sent
+ * by the SSL server.
+ */
+void SSL_add_untrusted_cert(SSL *ssl, X509 *cert);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Searches in serverCertificates list for the cert issuer and if not found
+ * and Authority Info Access of cert provides a URI return it.
+ */
+const char *uriOfIssuerIfMissing(X509 *cert, Ssl::X509_STACK_Pointer const &serverCertificates);
+
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Fill URIs queue with the uris of missing certificates from serverCertificate chain
+ * if this information provided by Authority Info Access.
+ */
+void missingChainCertificatesUrls(std::queue<SBuf> &URIs, Ssl::X509_STACK_Pointer const &serverCertificates);
+
/**
\ingroup ServerProtocolSSLAPI
* Generate a certificate to be used as untrusted signing certificate, based on a trusted CA
*/
void addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *certList);
+/**
+ \ingroup ServerProtocolSSLAPI
+ * Configures sslContext to use squid untrusted certificates internal list
+ * to complete certificate chains when verifies SSL servers certificates.
+ */
+void useSquidUntrusted(SSL_CTX *sslContext);
+
/**
\ingroup ServerProtocolSSLAPI
* Read certificate, private key and any certificates which must be chained from files.