/*
- * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "squid.h"
#include "acl/FilledChecklist.h"
#include "comm/Loops.h"
+#include "Downloader.h"
#include "errorpage.h"
#include "fde.h"
+#include "http/Stream.h"
#include "HttpRequest.h"
#include "security/NegotiationHistory.h"
#include "security/PeerConnector.h"
callback(aCallback),
negotiationTimeout(timeout),
startTime(squid_curtime),
- useCertValidator_(true)
+ useCertValidator_(true),
+ certsDownloads(0)
{
debugs(83, 5, "Security::PeerConnector constructed, this=" << (void*)this);
// if this throws, the caller's cb dialer is not our CbDialer
AsyncJob::start();
Security::SessionPointer tmp;
- if (prepareSocket() && initializeTls(tmp))
- negotiateSsl();
+ if (prepareSocket() && initialize(tmp))
+ negotiate();
else
mustStop("Security::PeerConnector TLS socket initialize failed");
}
}
bool
-Security::PeerConnector::initializeTls(Security::SessionPointer &serverSession)
+Security::PeerConnector::initialize(Security::SessionPointer &serverSession)
{
#if USE_OPENSSL
- Security::ContextPtr sslContext(getSslContext());
- assert(sslContext);
+ Security::ContextPointer ctx(getTlsContext());
+ assert(ctx);
- if (!Ssl::CreateClient(sslContext, serverConnection(), "server https start")) {
+ if (!Ssl::CreateClient(ctx, serverConnection(), "server https start")) {
+ const auto xerrno = errno;
+ const auto ssl_error = ERR_get_error();
ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw());
- anErr->xerrno = errno;
- debugs(83, DBG_IMPORTANT, "Error allocating SSL handle: " << ERR_error_string(ERR_get_error(), NULL));
+ anErr->xerrno = xerrno;
+ debugs(83, DBG_IMPORTANT, "Error allocating TLS handle: " << Security::ErrorString(ssl_error));
noteNegotiationDone(anErr);
bail(anErr);
return false;
void
Security::PeerConnector::recordNegotiationDetails()
{
-#if USE_OPENSSL
const int fd = serverConnection()->fd;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
+ Security::SessionPointer session(fd_table[fd].ssl);
// retrieve TLS server negotiated information if any
- serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(ssl);
+ serverConnection()->tlsNegotiations()->retrieveNegotiatedInfo(session);
+
+#if USE_OPENSSL
// retrieve TLS parsed extra info
- BIO *b = SSL_get_rbio(ssl);
- Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(b->ptr);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *bio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
if (const Security::TlsDetails::Pointer &details = bio->receivedHelloDetails())
serverConnection()->tlsNegotiations()->retrieveParsedInfo(details);
#endif
}
void
-Security::PeerConnector::negotiateSsl()
+Security::PeerConnector::negotiate()
{
- if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing())
+ if (!Comm::IsConnOpen(serverConnection()))
return;
-#if USE_OPENSSL
const int fd = serverConnection()->fd;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
- const int result = SSL_connect(ssl);
+ if (fd_table[fd].closing())
+ return;
+
+#if USE_OPENSSL
+ const int result = SSL_connect(fd_table[fd].ssl.get());
#else
const int result = -1;
#endif
#if USE_OPENSSL
if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
const int fd = serverConnection()->fd;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
+ Security::SessionPointer session(fd_table[fd].ssl);
Ssl::CertValidationRequest validationRequest;
// WARNING: Currently we do not use any locking for any of the
// members of the Ssl::CertValidationRequest class. In this code the
// Ssl::CertValidationRequest object used only to pass data to
// Ssl::CertValidationHelper::submit method.
- validationRequest.ssl = ssl;
- validationRequest.domainName = request->url.host();
- if (Ssl::CertErrors *errs = static_cast<Ssl::CertErrors *>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors)))
+ validationRequest.ssl = session.get();
+ if (SBuf *dName = (SBuf *)SSL_get_ex_data(session.get(), ssl_ex_index_server))
+ validationRequest.domainName = dName->c_str();
+ if (Security::CertErrors *errs = static_cast<Security::CertErrors *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors)))
// validationRequest disappears on return so no need to cbdataReference
validationRequest.errors = errs;
- else
- validationRequest.errors = NULL;
try {
debugs(83, 5, "Sending SSL certificate for validation to ssl_crtvd.");
AsyncCall::Pointer call = asyncCall(83,5, "Security::PeerConnector::sslCrtvdHandleReply", Ssl::CertValidationHelper::CbDialer(this, &Security::PeerConnector::sslCrtvdHandleReply, nullptr));
return;
}
- debugs(83,5, request->url.host() << " cert validation result: " << validationResponse->resultCode);
+ if (Debug::Enabled(83, 5)) {
+ Security::SessionPointer ssl(fd_table[serverConnection()->fd].ssl);
+ SBuf *server = static_cast<SBuf *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_server));
+ debugs(83,5, RawPointer("host", server) << " cert validation result: " << validationResponse->resultCode);
+ }
if (validationResponse->resultCode == ::Helper::Error) {
- if (Ssl::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
- Security::SessionPtr ssl = fd_table[serverConnection()->fd].ssl.get();
- Ssl::CertErrors *oldErrs = static_cast<Ssl::CertErrors*>(SSL_get_ex_data(ssl, ssl_ex_index_ssl_errors));
- SSL_set_ex_data(ssl, ssl_ex_index_ssl_errors, (void *)errs);
+ if (Security::CertErrors *errs = sslCrtvdCheckForErrors(*validationResponse, errDetails)) {
+ Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
+ Security::CertErrors *oldErrs = static_cast<Security::CertErrors*>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_errors));
+ SSL_set_ex_data(session.get(), ssl_ex_index_ssl_errors, (void *)errs);
delete oldErrs;
}
} else if (validationResponse->resultCode != ::Helper::Okay)
#if USE_OPENSSL
/// Checks errors in the cert. validator response against sslproxy_cert_error.
/// The first honored error, if any, is returned via errDetails parameter.
-/// The method returns all seen errors except SSL_ERROR_NONE as Ssl::CertErrors.
-Ssl::CertErrors *
+/// The method returns all seen errors except SSL_ERROR_NONE as Security::CertErrors.
+Security::CertErrors *
Security::PeerConnector::sslCrtvdCheckForErrors(Ssl::CertValidationResponse const &resp, Ssl::ErrorDetail *& errDetails)
{
- Ssl::CertErrors *errs = NULL;
-
ACLFilledChecklist *check = NULL;
if (acl_access *acl = ::Config.ssl_client.cert_error) {
check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
check->al = al;
}
- Security::SessionPtr ssl = fd_table[serverConnection()->fd].ssl.get();
+ Security::CertErrors *errs = nullptr;
+ Security::SessionPointer session(fd_table[serverConnection()->fd].ssl);
typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
debugs(83, 7, "Error item: " << i->error_no << " " << i->error_reason);
if (!errDetails) {
bool allowed = false;
if (check) {
- check->sslErrors = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get(), i->error_depth));
+ check->sslErrors = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
if (check->fastCheck() == ACCESS_ALLOWED)
allowed = true;
}
} else {
debugs(83, 5, "confirming SSL error " << i->error_no);
X509 *brokenCert = i->cert.get();
- Security::CertPointer peerCert(SSL_get_peer_certificate(ssl));
+ Security::CertPointer peerCert(SSL_get_peer_certificate(session.get()));
const char *aReason = i->error_reason.empty() ? NULL : i->error_reason.c_str();
errDetails = new Ssl::ErrorDetail(i->error_no, peerCert.get(), brokenCert, aReason);
}
}
if (!errs)
- errs = new Ssl::CertErrors(Ssl::CertError(i->error_no, i->cert.get(), i->error_depth));
+ errs = new Security::CertErrors(Security::CertError(i->error_no, i->cert, i->error_depth));
else
- errs->push_back_unique(Ssl::CertError(i->error_no, i->cert.get(), i->error_depth));
+ errs->push_back_unique(Security::CertError(i->error_no, i->cert, i->error_depth));
}
if (check)
delete check;
void
Security::PeerConnector::NegotiateSsl(int, void *data)
{
- PeerConnector *pc = static_cast<PeerConnector*>(data);
+ PeerConnector *pc = static_cast<Security::PeerConnector *>(data);
// Use job calls to add done() checks and other job logic/protections.
- CallJobHere(83, 7, pc, Security::PeerConnector, negotiateSsl);
+ CallJobHere(83, 7, pc, Security::PeerConnector, negotiate);
}
void
#if USE_OPENSSL
const int fd = serverConnection()->fd;
unsigned long ssl_lib_error = SSL_ERROR_NONE;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
- const int ssl_error = SSL_get_error(ssl, ret);
+ Security::SessionPointer session(fd_table[fd].ssl);
+ const int ssl_error = SSL_get_error(session.get(), ret);
switch (ssl_error) {
case SSL_ERROR_WANT_READ:
// Log connection details, if any
recordNegotiationDetails();
- noteSslNegotiationError(ret, ssl_error, ssl_lib_error);
+ noteNegotiationError(ret, ssl_error, ssl_lib_error);
#endif
}
void
Security::PeerConnector::noteWantRead()
{
- setReadTimeout();
const int fd = serverConnection()->fd;
+#if USE_OPENSSL
+ Security::SessionPointer session(fd_table[fd].ssl);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
+ 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
+ Security::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
+ Security::PeerConnector::NegotiateSsl(fd, this);
+ return;
+ }
+ }
+#endif
+ setReadTimeout();
Comm::SetSelect(fd, COMM_SELECT_READ, &NegotiateSsl, this, 0);
}
}
void
-Security::PeerConnector::noteSslNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
+Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error, const int ssl_lib_error)
{
#if USE_OPENSSL // not used unless OpenSSL enabled.
#if defined(EPROTO)
const int fd = serverConnection()->fd;
debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
- ": " << ERR_error_string(ssl_lib_error, NULL) << " (" <<
+ ": " << Security::ErrorString(ssl_lib_error) << " (" <<
ssl_error << "/" << ret << "/" << errno << ")");
ErrorState *anErr = NULL;
anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL);
anErr->xerrno = sysErrNo;
- Security::SessionPtr ssl = fd_table[fd].ssl.get();
- Ssl::ErrorDetail *errFromFailure = (Ssl::ErrorDetail *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_error_detail);
+ Security::SessionPointer session(fd_table[fd].ssl);
+ Ssl::ErrorDetail *errFromFailure = static_cast<Ssl::ErrorDetail *>(SSL_get_ex_data(session.get(), ssl_ex_index_ssl_error_detail));
if (errFromFailure != NULL) {
// The errFromFailure is attached to the ssl object
// and will be released when ssl object destroyed.
anErr->detail = new Ssl::ErrorDetail(*errFromFailure);
} else {
// server_cert can be NULL here
- X509 *server_cert = SSL_get_peer_certificate(ssl);
+ X509 *server_cert = SSL_get_peer_certificate(session.get());
anErr->detail = new Ssl::ErrorDetail(SQUID_ERR_SSL_HANDSHAKE, server_cert, NULL);
X509_free(server_cert);
}
}
if (serverConn != NULL)
buf.appendf(" FD %d", serverConn->fd);
- buf.appendf(" %s%u]", id.Prefix, id.value);
+ buf.appendf(" %s%u]", id.prefix(), id.value);
buf.terminate();
return buf.content();
}
+#if USE_OPENSSL
+/// CallDialer to allow use Downloader objects within PeerConnector class.
+class PeerConnectorCertDownloaderDialer: public Downloader::CbDialer
+{
+public:
+ typedef void (Security::PeerConnector::*Method)(SBuf &object, int status);
+
+ PeerConnectorCertDownloaderDialer(Method method, Security::PeerConnector *pc):
+ method_(method),
+ peerConnector_(pc) {}
+
+ /* CallDialer API */
+ virtual bool canDial(AsyncCall &call) { return peerConnector_.valid(); }
+ virtual void dial(AsyncCall &call) { ((&(*peerConnector_))->*method_)(object, status); }
+ Method method_; ///< The Security::PeerConnector method to dial
+ CbcPointer<Security::PeerConnector> peerConnector_; ///< The Security::PeerConnector object
+};
+
+void
+Security::PeerConnector::startCertDownloading(SBuf &url)
+{
+ AsyncCall::Pointer certCallback = asyncCall(81, 4,
+ "Security::PeerConnector::certDownloadingDone",
+ PeerConnectorCertDownloaderDialer(&Security::PeerConnector::certDownloadingDone, this));
+
+ const Downloader *csd = (request ? dynamic_cast<const Downloader*>(request->downloader.valid()) : nullptr);
+ Downloader *dl = new Downloader(url, certCallback, csd ? csd->nestedLevel() + 1 : 1);
+ AsyncJob::Start(dl);
+}
+
+void
+Security::PeerConnector::certDownloadingDone(SBuf &obj, int downloadStatus)
+{
+ ++certsDownloads;
+ debugs(81, 5, "Certificate downloading status: " << downloadStatus << " certificate size: " << obj.length());
+
+ // get ServerBio from SSL object
+ const int fd = serverConnection()->fd;
+ Security::SessionPointer session(fd_table[fd].ssl);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
+
+ // Parse Certificate. Assume that it is in DER format.
+ // According to RFC 4325:
+ // The server must provide a DER encoded certificate or a collection
+ // collection of certificates in a "certs-only" CMS message.
+ // The applications MUST accept DER encoded certificates and SHOULD
+ // be able to accept collection of certificates.
+ // TODO: support collection of certificates
+ 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 Security::CertList &certsList = srvBio->serverCertificatesIfAny();
+ if (const char *issuerUri = Ssl::uriOfIssuerIfMissing(cert, certsList)) {
+ urlsOfMissingCerts.push(SBuf(issuerUri));
+ }
+ Ssl::SSL_add_untrusted_cert(session.get(), cert);
+ }
+
+ // Check if there are URIs to download from and if yes start downloading
+ // the first in queue.
+ if (urlsOfMissingCerts.size() && certsDownloads <= MaxCertsDownloads) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return;
+ }
+
+ srvBio->holdRead(false);
+ Security::PeerConnector::NegotiateSsl(serverConnection()->fd, this);
+}
+
+bool
+Security::PeerConnector::checkForMissingCertificates()
+{
+ // Check for nested SSL certificates downloads. For example when the
+ // certificate located in an SSL site which requires to download a
+ // a missing certificate (... from an SSL site which requires to ...).
+
+ const Downloader *csd = (request ? request->downloader.get() : nullptr);
+ if (csd && csd->nestedLevel() >= MaxNestedDownloads)
+ return false;
+
+ const int fd = serverConnection()->fd;
+ Security::SessionPointer session(fd_table[fd].ssl);
+ BIO *b = SSL_get_rbio(session.get());
+ Ssl::ServerBio *srvBio = static_cast<Ssl::ServerBio *>(BIO_get_data(b));
+ const Security::CertList &certs = srvBio->serverCertificatesIfAny();
+
+ if (certs.size()) {
+ debugs(83, 5, "SSL server sent " << certs.size() << " certificates");
+ Ssl::missingChainCertificatesUrls(urlsOfMissingCerts, certs);
+ if (urlsOfMissingCerts.size()) {
+ startCertDownloading(urlsOfMissingCerts.front());
+ urlsOfMissingCerts.pop();
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif //USE_OPENSSL
+