[squid-dev] [PATCH] initial GnuTLS support for encrypted server connections
Amos Jeffries
squid3 at treenet.co.nz
Thu Jan 19 07:25:56 UTC 2017
Ar, forgot the patch again. Please find it attached.
Amos
-------------- next part --------------
=== modified file 'src/Makefile.am'
--- src/Makefile.am 2017-01-08 05:12:44 +0000
+++ src/Makefile.am 2017-01-12 13:05:40 +0000
@@ -3022,44 +3022,44 @@
$(TESTSOURCES) \
SquidMath.cc \
SquidMath.h \
swap_log_op.cc
tests_testUfs_LDADD = \
http/libhttp.la \
parser/libparser.la \
CommCalls.o \
ident/libident.la \
acl/libacls.la \
acl/libstate.la \
acl/libapi.la \
libsquid.la \
ip/libip.la \
fs/libfs.la \
mgr/libmgr.la \
$(REPL_OBJS) \
acl/libacls.la \
DiskIO/libdiskio.la \
acl/libapi.la \
+ anyp/libanyp.la \
$(SSL_LIBS) \
ipc/libipc.la \
comm/libcomm.la \
- anyp/libanyp.la \
dns/libdns.la \
base/libbase.la \
ip/libip.la \
mem/libmem.la \
store/libstore.la \
$(ADAPTATION_LIBS) \
sbuf/libsbuf.la \
$(top_builddir)/lib/libmisccontainers.la \
$(top_builddir)/lib/libmiscencoding.la \
$(top_builddir)/lib/libmiscutil.la \
$(NETTLELIB) \
$(REGEXLIB) \
$(SSLLIB) \
$(LIBCPPUNIT_LIBS) \
$(COMPAT_LIB) \
$(XTRA_LIBS)
tests_testUfs_LDFLAGS = $(LIBADD_DL)
tests_testUfs_DEPENDENCIES = \
$(SWAP_TEST_DS)
=== modified file 'src/client_side.cc'
--- src/client_side.cc 2017-01-01 00:12:22 +0000
+++ src/client_side.cc 2017-01-14 15:35:16 +0000
@@ -2540,47 +2540,46 @@
if (params.flag != Comm::OK) {
// Its possible the call was still queued when the client disconnected
debugs(33, 2, s->listenConn << ": accept failure: " << xstrerr(params.xerrno));
return;
}
debugs(33, 4, params.conn << ": accepted");
fd_note(params.conn->fd, "client http connect");
if (s->tcp_keepalive.enabled)
commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, s->tcp_keepalive.interval, s->tcp_keepalive.timeout);
++incoming_sockets_accepted;
// Socket is ready, setup the connection manager to start using it
auto *srv = Http::NewServer(xact);
AsyncJob::Start(srv); // usually async-calls readSomeData()
}
#if USE_OPENSSL
-
-/** Create SSL connection structure and update fd_table */
+/// Create TLS connection structure and update fd_table
static bool
httpsCreate(const Comm::ConnectionPointer &conn, const Security::ContextPointer &ctx)
{
- if (Ssl::CreateServer(ctx, conn, "client https start")) {
- debugs(33, 5, "will negotate SSL on " << conn);
+ if (Security::CreateServerSession(ctx, conn, "client https start")) {
+ debugs(33, 5, "will negotiate TLS on " << conn);
return true;
}
conn->close();
return false;
}
/**
*
* \retval 1 on success
* \retval 0 when needs more data
* \retval -1 on error
*/
static int
Squid_SSL_accept(ConnStateData *conn, PF *callback)
{
int fd = conn->clientConnection->fd;
auto ssl = fd_table[fd].ssl.get();
int ret;
=== modified file 'src/comm.cc'
--- src/comm.cc 2017-01-01 00:12:22 +0000
+++ src/comm.cc 2017-01-16 12:39:06 +0000
@@ -747,44 +747,41 @@
if (n < 0) {
int xerrno = errno;
debugs(5, 3, "FD " << fd << " read: " << xstrerr(xerrno));
}
comm_close(fd);
}
static void
commLingerTimeout(const FdeCbParams ¶ms)
{
debugs(5, 3, "commLingerTimeout: FD " << params.fd);
comm_close(params.fd);
}
/*
* Inspired by apache
*/
void
comm_lingering_close(int fd)
{
-#if USE_OPENSSL
- if (fd_table[fd].ssl)
- ssl_shutdown_method(fd_table[fd].ssl);
-#endif
+ Security::SessionSendGoodbye(fd_table[fd].ssl);
if (shutdown(fd, 1) < 0) {
comm_close(fd);
return;
}
fd_note(fd, "lingering close");
AsyncCall::Pointer call = commCbCall(5,4, "commLingerTimeout", FdeCbPtrFun(commLingerTimeout, NULL));
debugs(5, 3, HERE << "FD " << fd << " timeout " << timeout);
assert(fd_table[fd].flags.open);
if (callback != NULL) {
typedef FdeCbParams Params;
Params ¶ms = GetCommParams<Params>(callback);
params.fd = fd;
fd_table[fd].timeoutHandler = callback;
fd_table[fd].timeout = squid_curtime + static_cast<time_t>(10);
}
Comm::SetSelect(fd, COMM_SELECT_READ, commLingerClose, NULL, 0);
@@ -808,48 +805,45 @@
debugs(50, DBG_CRITICAL, "ERROR: Closing " << conn << " with TCP RST: " << xstrerr(xerrno));
}
conn->close();
}
// Legacy close function.
void
old_comm_reset_close(int fd)
{
struct linger L;
L.l_onoff = 1;
L.l_linger = 0;
if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &L, sizeof(L)) < 0) {
int xerrno = errno;
debugs(50, DBG_CRITICAL, "ERROR: Closing FD " << fd << " with TCP RST: " << xstrerr(xerrno));
}
comm_close(fd);
}
-#if USE_OPENSSL
void
-commStartSslClose(const FdeCbParams ¶ms)
+commStartTlsClose(const FdeCbParams ¶ms)
{
- assert(fd_table[params.fd].ssl);
- ssl_shutdown_method(fd_table[params.fd].ssl.get());
+ Security::SessionSendGoodbye(fd_table[params.fd].ssl);
}
-#endif
void
comm_close_complete(const FdeCbParams ¶ms)
{
fde *F = &fd_table[params.fd];
F->ssl.reset();
F->dynamicTlsContext.reset();
fd_close(params.fd); /* update fdstat */
close(params.fd);
++ statCounter.syscalls.sock.closes;
/* When one connection closes, give accept() a chance, if need be */
Comm::AcceptLimiter::Instance().kick();
}
/*
* Close the socket fd.
*
* + call write handlers with ERR_CLOSING
@@ -873,49 +867,47 @@
if (F->closing())
return;
/* XXX: is this obsolete behind F->closing() ? */
if ( (shutting_down || reconfiguring) && (!F->flags.open || F->type == FD_FILE))
return;
/* The following fails because ipc.c is doing calls to pipe() to create sockets! */
if (!isOpen(fd)) {
debugs(50, DBG_IMPORTANT, HERE << "BUG 3556: FD " << fd << " is not an open socket.");
// XXX: do we need to run close(fd) or fd_close(fd) here?
return;
}
assert(F->type != FD_FILE);
PROF_start(comm_close);
F->flags.close_request = true;
-#if USE_OPENSSL
if (F->ssl) {
- AsyncCall::Pointer startCall=commCbCall(5,4, "commStartSslClose",
- FdeCbPtrFun(commStartSslClose, NULL));
+ AsyncCall::Pointer startCall=commCbCall(5,4, "commStartTlsClose",
+ FdeCbPtrFun(commStartTlsClose, nullptr));
FdeCbParams &startParams = GetCommParams<FdeCbParams>(startCall);
startParams.fd = fd;
ScheduleCallHere(startCall);
}
-#endif
// a half-closed fd may lack a reader, so we stop monitoring explicitly
if (commHasHalfClosedMonitor(fd))
commStopHalfClosedMonitor(fd);
commUnsetFdTimeout(fd);
// notify read/write handlers after canceling select reservations, if any
if (COMMIO_FD_WRITECB(fd)->active()) {
Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
COMMIO_FD_WRITECB(fd)->finish(Comm::ERR_CLOSING, errno);
}
if (COMMIO_FD_READCB(fd)->active()) {
Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
COMMIO_FD_READCB(fd)->finish(Comm::ERR_CLOSING, errno);
}
#if USE_DELAY_POOLS
if (ClientInfo *clientInfo = F->clientInfo) {
if (clientInfo->selectWaiting) {
clientInfo->selectWaiting = false;
=== modified file 'src/security/BlindPeerConnector.cc'
--- src/security/BlindPeerConnector.cc 2017-01-01 00:12:22 +0000
+++ src/security/BlindPeerConnector.cc 2017-01-14 07:20:47 +0000
@@ -15,63 +15,68 @@
#include "neighbors.h"
#include "security/BlindPeerConnector.h"
#include "security/NegotiationHistory.h"
#include "SquidConfig.h"
CBDATA_NAMESPACED_CLASS_INIT(Security, BlindPeerConnector);
Security::ContextPointer
Security::BlindPeerConnector::getTlsContext()
{
if (const CachePeer *peer = serverConnection()->getPeer()) {
assert(peer->secure.encryptTransport);
return peer->sslContext;
}
return ::Config.ssl_client.sslContext;
}
bool
Security::BlindPeerConnector::initialize(Security::SessionPointer &serverSession)
{
- if (!Security::PeerConnector::initialize(serverSession))
+ if (!Security::PeerConnector::initialize(serverSession)) {
+ debugs(83, 5, "Security::PeerConnector::initialize failed");
return false;
+ }
if (const CachePeer *peer = serverConnection()->getPeer()) {
assert(peer);
// NP: domain may be a raw-IP but it is now always set
assert(!peer->secure.sslDomain.isEmpty());
#if USE_OPENSSL
// const loss is okay here, ssl_ex_index_server is only read and not assigned a destructor
SBuf *host = new SBuf(peer->secure.sslDomain);
SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, host);
Security::SetSessionResumeData(serverSession, peer->sslSession);
} else {
SBuf *hostName = new SBuf(request->url.host());
SSL_set_ex_data(serverSession.get(), ssl_ex_index_server, (void*)hostName);
#endif
}
+
+ debugs(83, 5, "success");
return true;
}
void
Security::BlindPeerConnector::noteNegotiationDone(ErrorState *error)
{
if (error) {
+ debugs(83, 5, "error=" << (void*)error);
// XXX: forward.cc calls peerConnectSucceeded() after an OK TCP connect but
// we call peerConnectFailed() if SSL failed afterwards. Is that OK?
// It is not clear whether we should call peerConnectSucceeded/Failed()
// based on TCP results, SSL results, or both. And the code is probably not
// consistent in this aspect across tunnelling and forwarding modules.
if (CachePeer *p = serverConnection()->getPeer())
peerConnectFailed(p);
return;
}
if (auto *peer = serverConnection()->getPeer()) {
const int fd = serverConnection()->fd;
Security::MaybeGetSessionResumeData(fd_table[fd].ssl, peer->sslSession);
}
}
=== modified file 'src/security/PeerConnector.cc'
--- src/security/PeerConnector.cc 2017-01-01 00:12:22 +0000
+++ src/security/PeerConnector.cc 2017-01-16 12:35:30 +0000
@@ -41,116 +41,120 @@
debugs(83, 5, "Security::PeerConnector constructed, this=" << (void*)this);
// if this throws, the caller's cb dialer is not our CbDialer
Must(dynamic_cast<CbDialer*>(callback->getDialer()));
}
Security::PeerConnector::~PeerConnector()
{
debugs(83, 5, "Security::PeerConnector destructed, this=" << (void*)this);
}
bool Security::PeerConnector::doneAll() const
{
return (!callback || callback->canceled()) && AsyncJob::doneAll();
}
/// Preps connection and SSL state. Calls negotiate().
void
Security::PeerConnector::start()
{
AsyncJob::start();
+ debugs(83, 5, "this=" << (void*)this);
Security::SessionPointer tmp;
if (prepareSocket() && initialize(tmp))
negotiate();
else
mustStop("Security::PeerConnector TLS socket initialize failed");
}
void
Security::PeerConnector::commCloseHandler(const CommCloseCbParams ¶ms)
{
debugs(83, 5, "FD " << params.fd << ", Security::PeerConnector=" << params.data);
connectionClosed("Security::PeerConnector::commCloseHandler");
}
void
Security::PeerConnector::connectionClosed(const char *reason)
{
+ debugs(83, 5, reason << " socket closed/closing. this=" << (void*)this);
mustStop(reason);
callback = NULL;
}
bool
Security::PeerConnector::prepareSocket()
{
- const int fd = serverConnection()->fd;
- if (!Comm::IsConnOpen(serverConn) || fd_table[serverConn->fd].closing()) {
+ debugs(83, 5, serverConnection() << ", this=" << (void*)this);
+ if (!Comm::IsConnOpen(serverConnection()) || fd_table[serverConnection()->fd].closing()) {
connectionClosed("Security::PeerConnector::prepareSocket");
return false;
}
+ debugs(83, 5, serverConnection());
+
// watch for external connection closures
typedef CommCbMemFunT<Security::PeerConnector, CommCloseCbParams> Dialer;
closeHandler = JobCallback(9, 5, Dialer, this, Security::PeerConnector::commCloseHandler);
- comm_add_close_handler(fd, closeHandler);
+ comm_add_close_handler(serverConnection()->fd, closeHandler);
return true;
}
bool
Security::PeerConnector::initialize(Security::SessionPointer &serverSession)
{
-#if USE_OPENSSL
Security::ContextPointer ctx(getTlsContext());
- assert(ctx);
+ debugs(83, 5, serverConnection() << ", ctx=" << (void*)ctx.get());
- if (!Ssl::CreateClient(ctx, serverConnection(), "server https start")) {
+ if (!ctx || !Security::CreateClientSession(ctx, serverConnection(), "server https start")) {
const auto xerrno = errno;
- const auto ssl_error = ERR_get_error();
+ if (!ctx) {
+ debugs(83, DBG_IMPORTANT, "Error initializing TLS connection: No security context.");
+ } // else CreateClientSession() did the appropriate debugs() already
ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw());
anErr->xerrno = xerrno;
- debugs(83, DBG_IMPORTANT, "Error allocating TLS handle: " << Security::ErrorString(ssl_error));
noteNegotiationDone(anErr);
bail(anErr);
return false;
}
// A TLS/SSL session has now been created for the connection and stored in fd_table
serverSession = fd_table[serverConnection()->fd].ssl;
+ debugs(83, 5, serverConnection() << ", session=" << (void*)serverSession.get());
+#if USE_OPENSSL
// If CertValidation Helper used do not lookup checklist for errors,
// but keep a list of errors to send it to CertValidator
if (!Ssl::TheConfig.ssl_crt_validator) {
// Create the ACL check list now, while we have access to more info.
// The list is used in ssl_verify_cb() and is freed in ssl_free().
if (acl_access *acl = ::Config.ssl_client.cert_error) {
ACLFilledChecklist *check = new ACLFilledChecklist(acl, request.getRaw(), dash_str);
check->al = al;
// check->fd(fd); XXX: need client FD here
SSL_set_ex_data(serverSession.get(), ssl_ex_index_cert_error_check, check);
}
}
+#endif
return true;
-#else
- return false;
-#endif
}
void
Security::PeerConnector::setReadTimeout()
{
int timeToRead;
if (negotiationTimeout) {
const int timeUsed = squid_curtime - startTime;
const int timeLeft = max(0, static_cast<int>(negotiationTimeout - timeUsed));
timeToRead = min(static_cast<int>(::Config.Timeout.read), timeLeft);
} else
timeToRead = ::Config.Timeout.read;
AsyncCall::Pointer nil;
commSetConnTimeout(serverConnection(), timeToRead, nil);
}
void
Security::PeerConnector::recordNegotiationDetails()
{
const int fd = serverConnection()->fd;
@@ -162,71 +166,90 @@
#if USE_OPENSSL
// retrieve TLS parsed extra info
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::negotiate()
{
if (!Comm::IsConnOpen(serverConnection()))
return;
const int fd = serverConnection()->fd;
if (fd_table[fd].closing())
return;
#if USE_OPENSSL
- const int result = SSL_connect(fd_table[fd].ssl.get());
+ auto session = fd_table[fd].ssl.get();
+ debugs(83, 5, "SSL_connect session=" << (void*)session);
+ const int result = SSL_connect(session);
+ if (result <= 0) {
+#elif USE_GNUTLS
+ auto session = fd_table[fd].ssl.get();
+ const int result = gnutls_handshake(session);
+ debugs(83, 5, "gnutls_handshake session=" << (void*)session << ", result=" << result);
+
+ if (result == GNUTLS_E_SUCCESS) {
+ char *desc = gnutls_session_get_desc(session);
+ debugs(83, 2, serverConnection() << " TLS Session info: " << desc);
+ gnutls_free(desc);
+ }
+
+ if (result != GNUTLS_E_SUCCESS) {
+ // debug the TLS session state so far
+ auto descIn = gnutls_handshake_get_last_in(session);
+ debugs(83, 2, "handshake IN: " << gnutls_handshake_description_get_name(descIn));
+ auto descOut = gnutls_handshake_get_last_out(session);
+ debugs(83, 2, "handshake OUT: " << gnutls_handshake_description_get_name(descOut));
#else
- const int result = -1;
+ if (const int result = -1) {
#endif
- if (result <= 0) {
handleNegotiateError(result);
return; // we might be gone by now
}
recordNegotiationDetails();
if (!sslFinalized())
return;
callBack();
}
bool
Security::PeerConnector::sslFinalized()
{
#if USE_OPENSSL
if (Ssl::TheConfig.ssl_crt_validator && useCertValidator_) {
const int fd = serverConnection()->fd;
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
+ // WARNING: Currently we do not use any locking for 'errors' member
+ // of the Ssl::CertValidationRequest class. In this code the
// Ssl::CertValidationRequest object used only to pass data to
// Ssl::CertValidationHelper::submit method.
- validationRequest.ssl = session.get();
+ validationRequest.ssl = session;
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;
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));
Ssl::CertValidationHelper::GetInstance()->sslSubmit(validationRequest, call);
return false;
} catch (const std::exception &e) {
debugs(83, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtvd " <<
"request for " << validationRequest.domainName <<
" certificate: " << e.what() << "; will now block to " <<
"validate that certificate.");
// fall through to do blocking in-process generation.
ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
noteNegotiationDone(anErr);
bail(anErr);
@@ -343,178 +366,215 @@
}
if (check)
delete check;
return errs;
}
#endif
/// A wrapper for Comm::SetSelect() notifications.
void
Security::PeerConnector::NegotiateSsl(int, void *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, negotiate);
}
void
Security::PeerConnector::handleNegotiateError(const int ret)
{
-#if USE_OPENSSL
const int fd = serverConnection()->fd;
- unsigned long ssl_lib_error = SSL_ERROR_NONE;
- Security::SessionPointer session(fd_table[fd].ssl);
+ const Security::SessionPointer session(fd_table[fd].ssl);
+ unsigned long ssl_lib_error = ret;
+
+#if USE_OPENSSL
const int ssl_error = SSL_get_error(session.get(), ret);
switch (ssl_error) {
case SSL_ERROR_WANT_READ:
noteWantRead();
return;
case SSL_ERROR_WANT_WRITE:
noteWantWrite();
return;
case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
ssl_lib_error = ERR_get_error();
// proceed to the general error handling code
break;
default:
// no special error handling for all other errors
+ ssl_lib_error = SSL_ERROR_NONE;
+ break;
+ }
+
+#elif USE_GNUTLS
+ const int ssl_error = ret;
+
+ switch (ret) {
+ case GNUTLS_E_WARNING_ALERT_RECEIVED: {
+ auto alert = gnutls_alert_get(session.get());
+ debugs(83, DBG_IMPORTANT, "TLS ALERT: " << gnutls_alert_get_name(alert));
+ }
+ // drop through to next case
+
+ case GNUTLS_E_AGAIN:
+ case GNUTLS_E_INTERRUPTED:
+ if (gnutls_record_get_direction(session.get()) == 0)
+ noteWantRead();
+ else
+ noteWantWrite();
+ return;
+
+ default:
+ // no special error handling for all other errors
break;
}
+#else
+ // this avoids unused variable compiler warnings.
+ Must(!session);
+ const int ssl_error = ret;
+#endif
+
// Log connection details, if any
recordNegotiationDetails();
noteNegotiationError(ret, ssl_error, ssl_lib_error);
-#endif
}
void
Security::PeerConnector::noteWantRead()
{
const int fd = serverConnection()->fd;
+ debugs(83, 5, serverConnection());
#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::noteWantWrite()
{
const int fd = serverConnection()->fd;
+ debugs(83, 5, serverConnection());
Comm::SetSelect(fd, COMM_SELECT_WRITE, &NegotiateSsl, this, 0);
return;
}
void
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)
int sysErrNo = EPROTO;
#else
int sysErrNo = EACCES;
#endif
+#if USE_OPENSSL
// store/report errno when ssl_error is SSL_ERROR_SYSCALL, ssl_lib_error is 0, and ret is -1
if (ssl_error == SSL_ERROR_SYSCALL && ret == -1 && ssl_lib_error == 0)
sysErrNo = errno;
+#endif
+ int xerr = errno;
const int fd = serverConnection()->fd;
- debugs(83, DBG_IMPORTANT, "Error negotiating SSL on FD " << fd <<
+ debugs(83, DBG_IMPORTANT, "ERROR: negotiating TLS on FD " << fd <<
": " << Security::ErrorString(ssl_lib_error) << " (" <<
- ssl_error << "/" << ret << "/" << errno << ")");
+ ssl_error << "/" << ret << "/" << xerr << ")");
ErrorState *anErr = NULL;
if (request != NULL)
anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request.getRaw());
else
anErr = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, NULL);
anErr->xerrno = sysErrNo;
+#if USE_OPENSSL
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.
// Copy errFromFailure to a new Ssl::ErrorDetail object
anErr->detail = new Ssl::ErrorDetail(*errFromFailure);
} else {
// server_cert can be NULL here
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 (ssl_lib_error != SSL_ERROR_NONE)
anErr->detail->setLibError(ssl_lib_error);
+#endif
noteNegotiationDone(anErr);
bail(anErr);
-#endif
}
void
Security::PeerConnector::bail(ErrorState *error)
{
Must(error); // or the recepient will not know there was a problem
Must(callback != NULL);
CbDialer *dialer = dynamic_cast<CbDialer*>(callback->getDialer());
Must(dialer);
dialer->answer().error = error;
callBack();
// Our job is done. The callabck recepient will probably close the failed
// peer connection and try another peer or go direct (if possible). We
// can close the connection ourselves (our error notification would reach
// the recepient before the fd-closure notification), but we would rather
// minimize the number of fd-closure notifications and let the recepient
// manage the TCP state of the connection.
}
void
Security::PeerConnector::callBack()
{
+ debugs(83, 5, "TLS setup ended for " << serverConnection());
+
AsyncCall::Pointer cb = callback;
// Do this now so that if we throw below, swanSong() assert that we _tried_
// to call back holds.
callback = NULL; // this should make done() true
// remove close handler
comm_remove_close_handler(serverConnection()->fd, closeHandler);
CbDialer *dialer = dynamic_cast<CbDialer*>(cb->getDialer());
Must(dialer);
dialer->answer().conn = serverConnection();
ScheduleCallHere(cb);
}
void
Security::PeerConnector::swanSong()
{
// XXX: unregister fd-closure monitoring and CommSetSelect interest, if any
AsyncJob::swanSong();
if (callback != NULL) { // paranoid: we have left the caller waiting
=== modified file 'src/security/PeerOptions.cc'
--- src/security/PeerOptions.cc 2017-01-01 00:12:22 +0000
+++ src/security/PeerOptions.cc 2017-01-17 13:51:44 +0000
@@ -4,91 +4,117 @@
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#include "base/Packable.h"
#include "Debug.h"
#include "fatal.h"
#include "globals.h"
#include "parser/Tokenizer.h"
#include "Parsing.h"
#include "security/PeerOptions.h"
#if USE_OPENSSL
#include "ssl/support.h"
#endif
Security::PeerOptions Security::ProxyOutgoingConfig;
+Security::PeerOptions::PeerOptions()
+{
+ // init options consistent with an empty sslOptions
+ parseOptions();
+}
+
Security::PeerOptions::PeerOptions(const Security::PeerOptions &p) :
sslOptions(p.sslOptions),
caDir(p.caDir),
crlFile(p.crlFile),
sslCipher(p.sslCipher),
sslFlags(p.sslFlags),
sslDomain(p.sslDomain),
parsedOptions(p.parsedOptions),
parsedFlags(p.parsedFlags),
certs(p.certs),
caFiles(p.caFiles),
parsedCrl(p.parsedCrl),
sslVersion(p.sslVersion),
encryptTransport(p.encryptTransport)
{
memcpy(&flags, &p.flags, sizeof(flags));
}
+Security::PeerOptions &
+Security::PeerOptions::operator =(const Security::PeerOptions &p)
+{
+ sslOptions = p.sslOptions;
+ caDir = p.caDir;
+ crlFile = p.crlFile;
+ sslCipher = p.sslCipher;
+ sslFlags = p.sslFlags;
+ sslDomain = p.sslDomain;
+ parsedOptions = p.parsedOptions;
+ parsedFlags = p.parsedFlags;
+ certs = p.certs;
+ caFiles = p.caFiles;
+ parsedCrl = p.parsedCrl;
+ sslVersion = p.sslVersion;
+ encryptTransport = p.encryptTransport;
+ memcpy(&flags, &p.flags, sizeof(flags));
+ return *this;
+}
+
void
Security::PeerOptions::parse(const char *token)
{
if (!*token) {
// config says just "ssl" or "tls" (or "tls-")
encryptTransport = true;
return;
}
if (strncmp(token, "disable", 7) == 0) {
clear();
return;
}
if (strncmp(token, "cert=", 5) == 0) {
KeyData t;
t.privateKeyFile = t.certFile = SBuf(token + 5);
certs.emplace_back(t);
} else if (strncmp(token, "key=", 4) == 0) {
if (certs.empty() || certs.back().certFile.isEmpty()) {
fatal("cert= option must be set before key= is used.");
return;
}
KeyData &t = certs.back();
t.privateKeyFile = SBuf(token + 4);
} else if (strncmp(token, "version=", 8) == 0) {
debugs(0, DBG_PARSE_NOTE(1), "UPGRADE WARNING: SSL version= is deprecated. Use options= to limit protocols instead.");
sslVersion = xatoi(token + 8);
} else if (strncmp(token, "min-version=", 12) == 0) {
tlsMinVersion = SBuf(token + 12);
} else if (strncmp(token, "options=", 8) == 0) {
sslOptions = SBuf(token + 8);
- parsedOptions = parseOptions();
+ parseOptions();
} else if (strncmp(token, "cipher=", 7) == 0) {
sslCipher = SBuf(token + 7);
} else if (strncmp(token, "cafile=", 7) == 0) {
caFiles.emplace_back(SBuf(token + 7));
} else if (strncmp(token, "capath=", 7) == 0) {
caDir = SBuf(token + 7);
#if !USE_OPENSSL
debugs(3, DBG_PARSE_NOTE(1), "WARNING: capath= option requires --with-openssl.");
#endif
} else if (strncmp(token, "crlfile=", 8) == 0) {
crlFile = SBuf(token + 8);
loadCrlFile();
} else if (strncmp(token, "flags=", 6) == 0) {
if (parsedFlags != 0) {
debugs(3, DBG_PARSE_NOTE(1), "WARNING: Overwriting flags=" << sslFlags << " with " << SBuf(token + 6));
}
sslFlags = SBuf(token + 6);
parsedFlags = parseFlags();
} else if (strncmp(token, "default-ca=off", 14) == 0 || strncmp(token, "no-default-ca", 13) == 0) {
if (flags.tlsDefaultCa.configured() && flags.tlsDefaultCa)
@@ -150,83 +176,121 @@
// default OFF for listening ports
if (flags.tlsDefaultCa)
p->appendf(" %sdefault-ca", pfx);
else
p->appendf(" %sdefault-ca=off", pfx);
}
if (!flags.tlsNpn)
p->appendf(" %sno-npn", pfx);
}
void
Security::PeerOptions::updateTlsVersionLimits()
{
if (!tlsMinVersion.isEmpty()) {
::Parser::Tokenizer tok(tlsMinVersion);
int64_t v = 0;
if (tok.skip('1') && tok.skip('.') && tok.int64(v, 10, false, 1) && v <= 3) {
// only account for TLS here - SSL versions are handled by options= parameter
// avoid affecting options= parameter in cachemgr config report
+#if USE_OPENSSL
#if SSL_OP_NO_TLSv1
if (v > 0)
parsedOptions |= SSL_OP_NO_TLSv1;
#endif
#if SSL_OP_NO_TLSv1_1
if (v > 1)
parsedOptions |= SSL_OP_NO_TLSv1_1;
#endif
#if SSL_OP_NO_TLSv1_2
if (v > 2)
parsedOptions |= SSL_OP_NO_TLSv1_2;
#endif
+#elif USE_GNUTLS
+ // XXX: update parsedOptions directly to avoid polluting 'options=' dumps
+ SBuf add;
+ if (v > 0)
+ add.append(":-VERS-TLS1.0");
+ if (v > 1)
+ add.append(":-VERS-TLS1.1");
+ if (v > 2)
+ add.append(":-VERS-TLS1.2");
+
+ if (sslOptions.isEmpty())
+ add.chop(1); // remove the initial ':'
+ sslOptions.append(add);
+#endif
+
} else {
debugs(0, DBG_PARSE_NOTE(1), "WARNING: Unknown TLS minimum version: " << tlsMinVersion);
}
- } else if (sslVersion > 2) {
+ return;
+ }
+
+ if (sslVersion > 2) {
// backward compatibility hack for sslversion= configuration
// only use if tls-min-version=N.N is not present
// values 0-2 for auto and SSLv2 are not supported any longer.
// Do it this way so we DO cause changes to options= in cachemgr config report
- const char *add = NULL;
+ const char *add = nullptr;
switch (sslVersion) {
case 3:
- add = "NO_TLSv1,NO_TLSv1_1,NO_TLSv1_2";
+#if USE_OPENSSL
+ parsedOptions |= (SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2);
+#elif USE_GNUTLS
+ add = ":-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2";
+#endif
break;
case 4:
- add = "NO_SSLv3,NO_TLSv1_1,NO_TLSv1_2";
+#if USE_OPENSSL
+ parsedOptions |= (SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1_1|SSL_OP_NO_TLSv1_2);
+#elif USE_GNUTLS
+ add = ":+VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2";
+#endif
break;
case 5:
- add = "NO_SSLv3,NO_TLSv1,NO_TLSv1_2";
+#if USE_OPENSSL
+ parsedOptions |= (SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_2);
+#elif USE_GNUTLS
+ add = ":-VERS-TLS1.0:+VERS-TLS1.1:-VERS-TLS1.2";
+#endif
break;
case 6:
- add = "NO_SSLv3,NO_TLSv1,NO_TLSv1_1";
+#if USE_OPENSSL
+ parsedOptions |= (SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1|SSL_OP_NO_TLSv1_1);
+#elif USE_GNUTLS
+ add = ":-VERS-TLS1.0:-VERS-TLS1.1";
+#endif
break;
default: // nothing
break;
}
if (add) {
- if (!sslOptions.isEmpty())
- sslOptions.append(",",1);
- sslOptions.append(add, strlen(add));
+#if USE_GNUTLS // dont bother otherwise
+ if (sslOptions.isEmpty())
+ sslOptions.append(add+1, strlen(add+1));
+ else
+ sslOptions.append(add, strlen(add));
+#endif
}
sslVersion = 0; // prevent sslOptions being repeatedly appended
}
}
Security::ContextPointer
Security::PeerOptions::createBlankContext() const
{
Security::ContextPointer ctx;
#if USE_OPENSSL
Ssl::Initialize();
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
SSL_CTX *t = SSL_CTX_new(TLS_client_method());
#else
SSL_CTX *t = SSL_CTX_new(SSLv23_client_method());
#endif
if (!t) {
const auto x = ERR_get_error();
fatalf("Failed to allocate TLS client context: %s\n", Security::ErrorString(x));
@@ -240,51 +304,55 @@
fatalf("Failed to allocate TLS client context: %s\n", Security::ErrorString(x));
}
ctx.resetWithoutLocking(t);
#else
debugs(83, 1, "WARNING: Failed to allocate TLS client context: No TLS library");
#endif
return ctx;
}
Security::ContextPointer
Security::PeerOptions::createClientContext(bool setOptions)
{
updateTlsVersionLimits();
Security::ContextPointer t(createBlankContext());
if (t) {
#if USE_OPENSSL
+ // NP: GnuTLS uses 'priorities' which are set per-session instead.
+ SSL_CTX_set_options(t.get(), (setOptions ? parsedOptions : 0));
+
// XXX: temporary performance regression. c_str() data copies and prevents this being a const method
- Ssl::InitClientContext(t, *this, (setOptions ? parsedOptions : 0), parsedFlags);
+ Ssl::InitClientContext(t, *this, parsedFlags);
#endif
updateContextNpn(t);
updateContextCa(t);
updateContextCrl(t);
}
return t;
}
+#if USE_OPENSSL
/// set of options we can parse and what they map to
static struct ssl_option {
const char *name;
long value;
} ssl_options[] = {
#if SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
{
"NETSCAPE_REUSE_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
},
#endif
#if SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
{
"SSLREF2_REUSE_CERT_TYPE_BUG", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
},
#endif
#if SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
{
"MICROSOFT_BIG_SSLV3_BUFFER", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
@@ -380,52 +448,54 @@
"No_Compression", SSL_OP_NO_COMPRESSION
},
#endif
#if SSL_OP_NO_TICKET
{
"NO_TICKET", SSL_OP_NO_TICKET
},
#endif
#if SSL_OP_SINGLE_ECDH_USE
{
"SINGLE_ECDH_USE", SSL_OP_SINGLE_ECDH_USE
},
#endif
{
"", 0
},
{
NULL, 0
}
};
+#endif /* USE_OPENSSL */
/**
* Pre-parse TLS options= parameter to be applied when the TLS objects created.
* Options must not used in the case of peek or stare bump mode.
*/
-long
+void
Security::PeerOptions::parseOptions()
{
- long op = 0;
+#if USE_OPENSSL
::Parser::Tokenizer tok(sslOptions);
+ long op = 0;
- do {
+ while (!tok.atEnd()) {
enum {
MODE_ADD, MODE_REMOVE
} mode;
if (tok.skip('-') || tok.skip('!'))
mode = MODE_REMOVE;
else {
(void)tok.skip('+'); // default action is add. ignore if missing operator
mode = MODE_ADD;
}
static const CharacterSet optChars = CharacterSet("TLS-option", "_") + CharacterSet::ALPHA + CharacterSet::DIGIT;
int64_t hex = 0;
SBuf option;
long value = 0;
// Bug 4429: identify the full option name before determining text or numeric
if (tok.prefix(option, optChars)) {
// find the named option in our supported set
@@ -444,47 +514,64 @@
}
if (value) {
switch (mode) {
case MODE_ADD:
op |= value;
break;
case MODE_REMOVE:
op &= ~value;
break;
}
} else {
debugs(83, DBG_PARSE_NOTE(1), "ERROR: Unknown TLS option " << option);
}
static const CharacterSet delims("TLS-option-delim",":,");
if (!tok.skipAll(delims) && !tok.atEnd()) {
fatalf("Unknown TLS option '" SQUIDSBUFPH "'", SQUIDSBUFPRINT(tok.remaining()));
}
- } while (!tok.atEnd());
+ }
#if SSL_OP_NO_SSLv2
// compliance with RFC 6176: Prohibiting Secure Sockets Layer (SSL) Version 2.0
op = op | SSL_OP_NO_SSLv2;
#endif
- return op;
+ parsedOptions = op;
+
+#elif USE_GNUTLS
+ if (sslOptions.isEmpty()) {
+ parsedOptions.reset();
+ return;
+ }
+
+ const char *err = nullptr;
+ const char *priorities = sslOptions.c_str();
+ gnutls_priority_t op;
+ if (gnutls_priority_init(&op, priorities, &err) != GNUTLS_E_SUCCESS) {
+ fatalf("Unknown TLS option '%s'", err);
+ }
+ parsedOptions = Security::ParsedOptions(op, [](gnutls_priority_t p) {
+ gnutls_priority_deinit(p);
+ });
+#endif
}
/**
* Parses the TLS flags squid.conf parameter
*/
long
Security::PeerOptions::parseFlags()
{
if (sslFlags.isEmpty())
return 0;
static struct {
SBuf label;
long mask;
} flagTokens[] = {
{ SBuf("NO_DEFAULT_CA"), SSL_FLAG_NO_DEFAULT_CA },
{ SBuf("DELAYED_AUTH"), SSL_FLAG_DELAYED_AUTH },
{ SBuf("DONT_VERIFY_PEER"), SSL_FLAG_DONT_VERIFY_PEER },
{ SBuf("DONT_VERIFY_DOMAIN"), SSL_FLAG_DONT_VERIFY_DOMAIN },
{ SBuf("NO_SESSION_REUSE"), SSL_FLAG_NO_SESSION_REUSE },
@@ -625,26 +712,50 @@
if (parsedCrl.size()) {
for (auto &i : parsedCrl) {
if (!X509_STORE_add_crl(st, i.get()))
debugs(83, 2, "WARNING: Failed to add CRL");
else
verifyCrl = true;
}
}
#if X509_V_FLAG_CRL_CHECK
if ((parsedFlags & SSL_FLAG_VERIFY_CRL_ALL))
X509_STORE_set_flags(st, X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
else if (verifyCrl || (parsedFlags & SSL_FLAG_VERIFY_CRL))
X509_STORE_set_flags(st, X509_V_FLAG_CRL_CHECK);
#endif
#endif /* USE_OPENSSL */
}
void
+Security::PeerOptions::updateSessionOptions(Security::SessionPointer &s)
+{
+ // 'options=' value being set to session is a GnuTLS specific thing.
+#if !USE_OPENSSL && USE_GNUTLS
+ int x;
+ SBuf errMsg;
+ if (!parsedOptions) {
+ debugs(83, 5, "set GnuTLS default priority/options for session=" << s);
+ x = gnutls_set_default_priority(s.get());
+ static const SBuf defaults("default");
+ errMsg = defaults;
+ } else {
+ debugs(83, 5, "set GnuTLS options '" << sslOptions << "' for session=" << s);
+ x = gnutls_priority_set(s.get(), parsedOptions.get());
+ errMsg = sslOptions;
+ }
+
+ if (x != GNUTLS_E_SUCCESS) {
+ debugs(83, DBG_IMPORTANT, "ERROR: Failed to set TLS options (" << errMsg << "). error: " << Security::ErrorString(x));
+ }
+#endif
+}
+
+void
parse_securePeerOptions(Security::PeerOptions *opt)
{
while(const char *token = ConfigParser::NextToken())
opt->parse(token);
}
=== modified file 'src/security/PeerOptions.h'
--- src/security/PeerOptions.h 2017-01-01 00:12:22 +0000
+++ src/security/PeerOptions.h 2017-01-17 13:54:04 +0000
@@ -5,105 +5,111 @@
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#ifndef SQUID_SRC_SECURITY_PEEROPTIONS_H
#define SQUID_SRC_SECURITY_PEEROPTIONS_H
#include "base/YesNoNone.h"
#include "ConfigParser.h"
#include "security/KeyData.h"
class Packable;
namespace Security
{
/// TLS squid.conf settings for a remote server peer
class PeerOptions
{
public:
- PeerOptions() : parsedOptions(0), parsedFlags(0), sslVersion(0), encryptTransport(false) {}
+ PeerOptions();
PeerOptions(const PeerOptions &);
- virtual ~PeerOptions() = default;
+ PeerOptions &operator =(const PeerOptions &);
+ virtual ~PeerOptions() {}
/// parse a TLS squid.conf option
virtual void parse(const char *);
/// reset the configuration details to default
virtual void clear() {*this = PeerOptions();}
/// generate an unset security context object
virtual Security::ContextPointer createBlankContext() const;
/// generate a security client-context from these configured options
Security::ContextPointer createClientContext(bool setOptions);
/// sync the context options with tls-min-version=N configuration
void updateTlsVersionLimits();
/// setup the NPN extension details for the given context
void updateContextNpn(Security::ContextPointer &);
/// setup the CA details for the given context
void updateContextCa(Security::ContextPointer &);
/// setup the CRL details for the given context
void updateContextCrl(Security::ContextPointer &);
+ /// setup any library-specific options that can be set for the given session
+ void updateSessionOptions(Security::SessionPointer &);
+
/// output squid.conf syntax with 'pfx' prefix on parameters for the stored settings
virtual void dumpCfg(Packable *, const char *pfx) const;
private:
- long parseOptions();
+ void parseOptions(); ///< parsed value of sslOptions
long parseFlags();
void loadCrlFile();
public:
SBuf sslOptions; ///< library-specific options string
SBuf caDir; ///< path of directory containing a set of trusted Certificate Authorities
SBuf crlFile; ///< path of file containing Certificate Revoke List
SBuf sslCipher;
SBuf sslFlags; ///< flags defining what TLS operations Squid performs
SBuf sslDomain;
SBuf tlsMinVersion; ///< version label for minimum TLS version to permit
- long parsedOptions; ///< parsed value of sslOptions
- long parsedFlags; ///< parsed value of sslFlags
+ Security::ParsedOptions parsedOptions; ///< parsed value of sslOptions
+ long parsedFlags = 0; ///< parsed value of sslFlags
std::list<Security::KeyData> certs; ///< details from the cert= and file= config parameters
std::list<SBuf> caFiles; ///< paths of files containing trusted Certificate Authority
Security::CertRevokeList parsedCrl; ///< CRL to use when verifying the remote end certificate
protected:
- int sslVersion;
+ int sslVersion = 0;
/// flags governing Squid internal TLS operations
struct flags_ {
flags_() : tlsDefaultCa(true), tlsNpn(true) {}
+ flags_(const flags_ &) = default;
+ flags_ &operator =(const flags_ &) = default;
/// whether to use the system default Trusted CA when verifying the remote end certificate
YesNoNone tlsDefaultCa;
/// whether to use the TLS NPN extension on these connections
bool tlsNpn;
} flags;
public:
/// whether transport encryption (TLS/SSL) is to be used on connections to the peer
- bool encryptTransport;
+ bool encryptTransport = false;
};
/// configuration options for DIRECT server access
extern PeerOptions ProxyOutgoingConfig;
} // namespace Security
// parse the tls_outgoing_options directive
void parse_securePeerOptions(Security::PeerOptions *);
#define free_securePeerOptions(x) Security::ProxyOutgoingConfig.clear()
#define dump_securePeerOptions(e,n,x) do { (e)->appendf(n); (x).dumpCfg((e),""); (e)->append("\n",1); } while(false)
#endif /* SQUID_SRC_SECURITY_PEEROPTIONS_H */
=== modified file 'src/security/Session.cc'
--- src/security/Session.cc 2017-01-01 00:12:22 +0000
+++ src/security/Session.cc 2017-01-16 15:16:35 +0000
@@ -1,41 +1,214 @@
/*
* 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.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
/* DEBUG: section 83 TLS session management */
#include "squid.h"
#include "anyp/PortCfg.h"
#include "base/RunnersRegistry.h"
+#include "CachePeer.h"
#include "Debug.h"
+#include "fd.h"
+#include "fde.h"
#include "ipc/MemMap.h"
#include "security/Session.h"
#include "SquidConfig.h"
+#include "ssl/bio.h"
#define SSL_SESSION_ID_SIZE 32
#define SSL_SESSION_MAX_SIZE 10*1024
+#if USE_OPENSSL || USE_GNUTLS
+static int
+tls_read_method(int fd, char *buf, int len)
+{
+ auto session = fd_table[fd].ssl.get();
+ debugs(83, 3, "started for session=" << (void*)session);
+
+#if DONT_DO_THIS && USE_OPENSSL
+ if (!SSL_is_init_finished(session)) {
+ errno = ENOTCONN;
+ return -1;
+ }
+#endif
+
+#if USE_OPENSSL
+ int i = SSL_read(session, buf, len);
+#elif USE_GNUTLS
+ int i = gnutls_record_recv(session, buf, len);
+#endif
+
+ if (i > 0) {
+ debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
+ (void)VALGRIND_MAKE_MEM_DEFINED(buf, i);
+ }
+
+#if USE_OPENSSL
+ if (i > 0 && SSL_pending(session) > 0) {
+#elif USE_GNUTLS
+ if (i > 0 && gnutls_record_check_pending(session) > 0) {
+#endif
+ debugs(83, 2, "TLS FD " << fd << " is pending");
+ fd_table[fd].flags.read_pending = true;
+ } else
+ fd_table[fd].flags.read_pending = false;
+
+ return i;
+}
+
+static int
+tls_write_method(int fd, const char *buf, int len)
+{
+ auto session = fd_table[fd].ssl.get();
+ debugs(83, 3, "started for session=" << (void*)session);
+
+#if USE_OPENSSL
+ if (!SSL_is_init_finished(session)) {
+ errno = ENOTCONN;
+ return -1;
+ }
+#endif
+
+#if USE_OPENSSL
+ int i = SSL_write(session, buf, len);
+#elif USE_GNUTLS
+ int i = gnutls_record_send(session, buf, len);
+#endif
+
+ if (i > 0) {
+ debugs(83, 8, "TLS FD " << fd << " session=" << (void*)session << " " << i << " bytes");
+ }
+ return i;
+}
+#endif
+
+static bool
+CreateSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Security::Io::Type type, const char *squidCtx)
+{
+ if (!Comm::IsConnOpen(conn)) {
+ debugs(83, DBG_IMPORTANT, "Gone connection");
+ return false;
+ }
+
+#if USE_OPENSSL || USE_GNUTLS
+
+ const char *errAction = "with no TLS/SSL library";
+ int errCode = 0;
+#if USE_OPENSSL
+ Security::SessionPointer session(SSL_new(ctx.get()), [](SSL *p) {
+ debugs(83, 5, "SSL_free session=" << (void*)p);
+ SSL_free(p);
+ });
+ debugs(83, 5, "SSL_new session=" << (void*)session.get());
+ if (!session) {
+ errCode = ERR_get_error();
+ errAction = "failed to allocate handle";
+ }
+#elif USE_GNUTLS
+ gnutls_session_t tmp;
+ errCode = gnutls_init(&tmp, static_cast<unsigned int>(type) | GNUTLS_NONBLOCK);
+ Security::SessionPointer session(tmp, [](gnutls_session_t p) {
+ debugs(83, 5, "gnutls_deinit session=" << (void*)p);
+ gnutls_deinit(p);
+ });
+ debugs(83, 5, "gnutls_init " << (type == Security::Io::BIO_TO_SERVER ? "client" : "server" )<< " session=" << (void*)session.get());
+ if (errCode != GNUTLS_E_SUCCESS) {
+ session.reset();
+ errAction = "failed to initialize session";
+ }
+#endif
+
+ if (session) {
+ const int fd = conn->fd;
+
+#if USE_OPENSSL
+ // without BIO, we would call SSL_set_fd(ssl.get(), fd) instead
+ if (BIO *bio = Ssl::Bio::Create(fd, type)) {
+ Ssl::Bio::Link(session.get(), bio); // cannot fail
+#elif USE_GNUTLS
+ errCode = gnutls_credentials_set(session.get(), GNUTLS_CRD_CERTIFICATE, ctx.get());
+ if (errCode == GNUTLS_E_SUCCESS) {
+
+ if (auto *peer = conn->getPeer())
+ peer->secure.updateSessionOptions(session);
+ else
+ Security::ProxyOutgoingConfig.updateSessionOptions(session);
+
+ // NP: GnuTLS does not yet support the BIO operations
+ // this does the equivalent of SSL_set_fd() for now.
+ gnutls_transport_set_int(session.get(), fd);
+ gnutls_handshake_set_timeout(session.get(), GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
+#endif
+
+ debugs(83, 5, "link FD " << fd << " to TLS session=" << (void*)session.get());
+ fd_table[fd].ssl = session;
+ fd_table[fd].read_method = &tls_read_method;
+ fd_table[fd].write_method = &tls_write_method;
+ fd_note(fd, squidCtx);
+ return true;
+ }
+
+#if USE_OPENSSL
+ errCode = ERR_get_error();
+ errAction = "failed to initialize I/O";
+#elif USE_GNUTLS
+ errAction = "failed to assign credentials";
+#endif
+ }
+
+ debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
+ ": " << (errCode != 0 ? Security::ErrorString(errCode) : ""));
+#endif
+ return false;
+}
+
+bool
+Security::CreateClientSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
+{
+ return CreateSession(ctx, c, Security::Io::BIO_TO_SERVER, squidCtx);
+}
+
+bool
+Security::CreateServerSession(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
+{
+ return CreateSession(ctx, c, Security::Io::BIO_TO_CLIENT, squidCtx);
+}
+
+void
+Security::SessionSendGoodbye(const Security::SessionPointer &s)
+{
+ debugs(83, 5, "session=" << (void*)s.get());
+ if (s) {
+#if USE_OPENSSL
+ SSL_shutdown(s.get());
+#elif USE_GNUTLS
+ gnutls_bye(s.get(), GNUTLS_SHUT_RDWR);
+#endif
+ }
+}
+
bool
Security::SessionIsResumed(const Security::SessionPointer &s)
{
bool result = false;
#if USE_OPENSSL
result = SSL_session_reused(s.get()) == 1;
#elif USE_GNUTLS
result = gnutls_session_is_resumed(s.get()) != 0;
#endif
debugs(83, 7, "session=" << (void*)s.get() << ", query? answer: " << (result ? 'T' : 'F') );
return result;
}
void
Security::MaybeGetSessionResumeData(const Security::SessionPointer &s, Security::SessionStatePointer &data)
{
if (!SessionIsResumed(s)) {
#if USE_OPENSSL
// nil is valid for SSL_get1_session(), it cannot fail.
data.reset(SSL_get1_session(s.get()));
=== modified file 'src/security/Session.h'
--- src/security/Session.h 2017-01-01 00:12:22 +0000
+++ src/security/Session.h 2017-01-16 12:37:44 +0000
@@ -1,78 +1,81 @@
/*
* 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.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#ifndef SQUID_SRC_SECURITY_SESSION_H
#define SQUID_SRC_SECURITY_SESSION_H
+#include "base/HardFun.h"
+#include "comm/forward.h"
#include "security/LockingPointer.h"
#include <memory>
#if USE_OPENSSL
#if HAVE_OPENSSL_SSL_H
#include <openssl/ssl.h>
#endif
#endif
#if USE_GNUTLS
#if HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h>
#endif
#endif
namespace Security {
+/// Creates TLS Client connection structure (aka 'session' state) and initializes TLS/SSL I/O (Comm and BIO).
+/// On errors, emits DBG_IMPORTANT with details and returns false.
+bool CreateClientSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx);
+
+/// Creates TLS Server connection structure (aka 'session' state) and initializes TLS/SSL I/O (Comm and BIO).
+/// On errors, emits DBG_IMPORTANT with details and returns false.
+bool CreateServerSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx);
+
#if USE_OPENSSL
-CtoCpp1(SSL_free, SSL *);
-#if defined(CRYPTO_LOCK_SSL) // OpenSSL 1.0
-inline int SSL_up_ref(SSL *t) {if (t) CRYPTO_add(&t->references, 1, CRYPTO_LOCK_SSL); return 0;}
-#endif
-typedef Security::LockingPointer<SSL, Security::SSL_free_cpp, HardFun<int, SSL *, SSL_up_ref> > SessionPointer;
+typedef std::shared_ptr<SSL> SessionPointer;
typedef std::unique_ptr<SSL_SESSION, HardFun<void, SSL_SESSION*, &SSL_SESSION_free>> SessionStatePointer;
#elif USE_GNUTLS
-// Locks can be implemented attaching locks counter to gnutls_session_t
-// objects using the gnutls_session_set_ptr()/gnutls_session_get_ptr ()
-// library functions
-CtoCpp1(gnutls_deinit, gnutls_session_t);
-typedef Security::LockingPointer<struct gnutls_session_int, gnutls_deinit_cpp> SessionPointer;
+typedef std::shared_ptr<struct gnutls_session_int> SessionPointer;
// wrapper function to get around gnutls_free being a typedef
inline void squid_gnutls_free(void *d) {gnutls_free(d);}
typedef std::unique_ptr<gnutls_datum_t, HardFun<void, void*, &Security::squid_gnutls_free>> SessionStatePointer;
#else
-// use void* so we can check against NULL
-CtoCpp1(xfree, void *);
-typedef Security::LockingPointer<void, xfree_cpp> SessionPointer;
+typedef std::shared_ptr<void> SessionPointer;
typedef std::unique_ptr<int> SessionStatePointer;
#endif
+/// send the shutdown/bye notice for an active TLS session.
+void SessionSendGoodbye(const Security::SessionPointer &);
+
/// whether the session is a resumed one
bool SessionIsResumed(const Security::SessionPointer &);
/**
* When the session is not a resumed session, retrieve the details needed to
* resume a later connection and store them in 'data'. This may result in 'data'
* becoming a nil Pointer if no details exist or an error occurs.
*
* When the session is already a resumed session, do nothing and leave 'data'
* unhanged.
* XXX: is this latter behaviour always correct?
*/
void MaybeGetSessionResumeData(const Security::SessionPointer &, Security::SessionStatePointer &data);
/// Set the data for resuming a previous session.
/// Needs to be done before using the SessionPointer for a handshake.
void SetSessionResumeData(const Security::SessionPointer &, const Security::SessionStatePointer &);
} // namespace Security
=== modified file 'src/security/forward.h'
--- src/security/forward.h 2017-01-01 00:12:22 +0000
+++ src/security/forward.h 2017-01-17 04:47:37 +0000
@@ -91,29 +91,52 @@
class EncryptorAnswer;
/// Squid defined error code (<0), an error code returned by X.509 API, or SSL_ERROR_NONE
typedef int ErrorCode;
inline const char *ErrorString(const ErrorCode code) {
#if USE_OPENSSL
return ERR_error_string(code, nullptr);
#elif USE_GNUTLS
return gnutls_strerror(code);
#else
return "[no TLS library]";
#endif
}
/// set of Squid defined TLS error codes
/// \note using std::unordered_set ensures values are unique, with fast lookup
typedef std::unordered_set<Security::ErrorCode> Errors;
+namespace Io
+{
+ enum Type {
+#if USE_GNUTLS
+ // NP: this is odd looking but correct.
+ // 'to-client' means we are a server, and vice versa.
+ BIO_TO_CLIENT = GNUTLS_SERVER,
+ BIO_TO_SERVER = GNUTLS_CLIENT
+#else
+ BIO_TO_CLIENT = 6000,
+ BIO_TO_SERVER
+#endif
+ };
+
+} // namespace Io
+
class KeyData;
+
+#if !USE_OPENSSL && USE_GNUTLS
+typedef std::shared_ptr<struct gnutls_priority_st> ParsedOptions;
+#else
+typedef long ParsedOptions;
+#endif
+
class PeerConnector;
class PeerOptions;
class ServerOptions;
} // namespace Security
#endif /* SQUID_SRC_SECURITY_FORWARD_H */
=== modified file 'src/ssl/bio.cc'
--- src/ssl/bio.cc 2017-01-01 00:12:22 +0000
+++ src/ssl/bio.cc 2017-01-12 13:05:40 +0000
@@ -45,41 +45,41 @@
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
/// Initialization structure for the BIO table with
/// Squid-specific methods and BIO method wrappers.
static BIO_METHOD SquidMethods = {
BIO_TYPE_SOCKET,
"squid",
squid_bio_write,
squid_bio_read,
squid_bio_puts,
NULL, // squid_bio_gets not supported
squid_bio_ctrl,
squid_bio_create,
squid_bio_destroy,
NULL // squid_callback_ctrl not supported
};
#else
static BIO_METHOD *SquidMethods = NULL;
#endif
BIO *
-Ssl::Bio::Create(const int fd, Ssl::Bio::Type type)
+Ssl::Bio::Create(const int fd, Security::Io::Type type)
{
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
BIO_METHOD *useMethod = &SquidMethods;
#else
if (!SquidMethods) {
SquidMethods = BIO_meth_new(BIO_TYPE_SOCKET, "squid");
BIO_meth_set_write(SquidMethods, squid_bio_write);
BIO_meth_set_read(SquidMethods, squid_bio_read);
BIO_meth_set_puts(SquidMethods, squid_bio_puts);
BIO_meth_set_gets(SquidMethods, NULL);
BIO_meth_set_ctrl(SquidMethods, squid_bio_ctrl);
BIO_meth_set_create(SquidMethods, squid_bio_create);
BIO_meth_set_destroy(SquidMethods, squid_bio_destroy);
}
const BIO_METHOD *useMethod = SquidMethods;
#endif
if (BIO *bio = BIO_new(useMethod)) {
BIO_int_ctrl(bio, BIO_C_SET_FD, type, fd);
return bio;
@@ -558,41 +558,41 @@
/// implements puts() via write()
static int
squid_bio_puts(BIO *table, const char *str)
{
assert(str);
return squid_bio_write(table, str, strlen(str));
}
/// other BIO manipulations (those without dedicated callbacks in BIO table)
static long
squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2)
{
debugs(83, 5, table << ' ' << cmd << '(' << arg1 << ", " << arg2 << ')');
switch (cmd) {
case BIO_C_SET_FD: {
assert(arg2);
const int fd = *static_cast<int*>(arg2);
Ssl::Bio *bio;
- if (arg1 == Ssl::Bio::BIO_TO_SERVER)
+ if (arg1 == Security::Io::BIO_TO_SERVER)
bio = new Ssl::ServerBio(fd);
else
bio = new Ssl::ClientBio(fd);
assert(!BIO_get_data(table));
BIO_set_data(table, bio);
BIO_set_init(table, 1);
return 0;
}
case BIO_C_GET_FD:
if (BIO_get_init(table)) {
Ssl::Bio *bio = static_cast<Ssl::Bio*>(BIO_get_data(table));
assert(bio);
if (arg2)
*static_cast<int*>(arg2) = bio->fd();
return bio->fd();
}
return -1;
case BIO_CTRL_DUP:
=== modified file 'src/ssl/bio.h'
--- src/ssl/bio.h 2017-01-01 00:12:22 +0000
+++ src/ssl/bio.h 2017-01-12 13:05:40 +0000
@@ -1,76 +1,73 @@
/*
* 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.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#ifndef SQUID_SSL_BIO_H
#define SQUID_SSL_BIO_H
+#if USE_OPENSSL
+
#include "fd.h"
#include "security/Handshake.h"
#include <iosfwd>
#include <list>
#if HAVE_OPENSSL_BIO_H
#include <openssl/bio.h>
#endif
#include <string>
#include <type_traits>
namespace Ssl
{
/// BIO source and sink node, handling socket I/O and monitoring SSL state
class Bio
{
public:
- enum Type {
- BIO_TO_CLIENT = 6000,
- BIO_TO_SERVER
- };
-
explicit Bio(const int anFd);
virtual ~Bio();
/// Writes the given data to socket
virtual int write(const char *buf, int size, BIO *table);
/// Reads data from socket
virtual int read(char *buf, int size, BIO *table);
/// Flushes any buffered data to socket.
/// The Ssl::Bio does not buffer any data, so this method has nothing to do
virtual void flush(BIO *table) {}
int fd() const { return fd_; } ///< The SSL socket descriptor
/// Called by linked SSL connection whenever state changes, an alert
/// appears, or an error occurs. See SSL_set_info_callback().
virtual void stateChanged(const SSL *ssl, int where, int ret);
/// Creates a low-level BIO table, creates a high-level Ssl::Bio object
/// for a given socket, and then links the two together via BIO_C_SET_FD.
- static BIO *Create(const int fd, Type type);
+ static BIO *Create(const int fd, Security::Io::Type type);
/// Tells ssl connection to use BIO and monitor state via stateChanged()
static void Link(SSL *ssl, BIO *bio);
const SBuf &rBufData() {return rbuf;} ///< The buffered input data
protected:
const int fd_; ///< the SSL socket we are reading and writing
SBuf rbuf; ///< Used to buffer input data.
};
/// BIO node to handle socket IO for squid client side
/// If bumping is enabled this Bio detects and analyses client hello message
/// to retrieve the SSL features supported by the client
class ClientBio: public Bio
{
public:
explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), helloSize(0) {}
/// The ClientBio version of the Ssl::Bio::stateChanged method
/// When the client hello message retrieved, fill the
/// "features" member with the client provided informations.
@@ -185,22 +182,23 @@
Ssl::BumpMode bumpMode_;
/// The size of data stored in rbuf which passed to the openSSL
size_t rbufConsumePos;
Security::HandshakeParser parser_; ///< The TLS/SSL messages parser.
};
} // namespace Ssl
void
applyTlsDetailsToSSL(SSL *ssl, Security::TlsDetails::Pointer const &details, Ssl::BumpMode bumpMode);
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
// OpenSSL v1.0 bio compatibility functions
inline void *BIO_get_data(BIO *table) { return table->ptr; }
inline void BIO_set_data(BIO *table, void *data) { table->ptr = data; }
inline int BIO_get_init(BIO *table) { return table->init; }
inline void BIO_set_init(BIO *table, int init) { table->init = init; }
#endif
+#endif /* USE_OPENSSL */
#endif /* SQUID_SSL_BIO_H */
=== modified file 'src/ssl/cert_validate_message.cc'
--- src/ssl/cert_validate_message.cc 2017-01-01 00:12:22 +0000
+++ src/ssl/cert_validate_message.cc 2017-01-12 13:05:40 +0000
@@ -4,50 +4,50 @@
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#include "acl/FilledChecklist.h"
#include "globals.h"
#include "helper.h"
#include "security/CertError.h"
#include "ssl/cert_validate_message.h"
#include "ssl/ErrorDetail.h"
#include "ssl/support.h"
#include "util.h"
void
Ssl::CertValidationMsg::composeRequest(CertValidationRequest const &vcert)
{
body.clear();
body += Ssl::CertValidationMsg::param_host + "=" + vcert.domainName;
- STACK_OF(X509) *peerCerts = static_cast<STACK_OF(X509) *>(SSL_get_ex_data(vcert.ssl, ssl_ex_index_ssl_cert_chain));
+ STACK_OF(X509) *peerCerts = static_cast<STACK_OF(X509) *>(SSL_get_ex_data(vcert.ssl.get(), ssl_ex_index_ssl_cert_chain));
- if (const char *sslVersion = SSL_get_version(vcert.ssl))
+ if (const char *sslVersion = SSL_get_version(vcert.ssl.get()))
body += "\n" + Ssl::CertValidationMsg::param_proto_version + "=" + sslVersion;
- if (const char *cipherName = SSL_CIPHER_get_name(SSL_get_current_cipher(vcert.ssl)))
+ if (const char *cipherName = SSL_CIPHER_get_name(SSL_get_current_cipher(vcert.ssl.get())))
body += "\n" + Ssl::CertValidationMsg::param_cipher + "=" + cipherName;
if (!peerCerts)
- peerCerts = SSL_get_peer_cert_chain(vcert.ssl);
+ peerCerts = SSL_get_peer_cert_chain(vcert.ssl.get());
if (peerCerts) {
Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
for (int i = 0; i < sk_X509_num(peerCerts); ++i) {
X509 *cert = sk_X509_value(peerCerts, i);
PEM_write_bio_X509(bio.get(), cert);
body = body + "\n" + param_cert + xitoa(i) + "=";
char *ptr;
long len = BIO_get_mem_data(bio.get(), &ptr);
body.append(ptr, (ptr[len-1] == '\n' ? len - 1 : len));
if (!BIO_reset(bio.get())) {
// print an error?
}
}
}
if (vcert.errors) {
int i = 0;
for (const Security::CertErrors *err = vcert.errors; err; err = err->next, ++i) {
body +="\n";
=== modified file 'src/ssl/cert_validate_message.h'
--- src/ssl/cert_validate_message.h 2017-01-01 00:12:22 +0000
+++ src/ssl/cert_validate_message.h 2017-01-12 13:05:40 +0000
@@ -9,44 +9,43 @@
#ifndef SQUID_SSL_CERT_VALIDATE_MESSAGE_H
#define SQUID_SSL_CERT_VALIDATE_MESSAGE_H
#include "base/RefCount.h"
#include "helper/ResultCode.h"
#include "ssl/crtd_message.h"
#include "ssl/support.h"
#include <vector>
namespace Ssl
{
/**
* This class is used to hold the required informations to build
* a request message for the certificate validator helper
*/
class CertValidationRequest
{
public:
- SSL *ssl;
- Security::CertErrors *errors; ///< The list of errors detected
+ Security::SessionPointer ssl;
+ Security::CertErrors *errors = nullptr; ///< The list of errors detected
std::string domainName; ///< The server name
- CertValidationRequest() : ssl(NULL), errors(NULL) {}
};
/**
* This class is used to store informations found in certificate validation
* response messages read from certificate validator helper
*/
class CertValidationResponse: public RefCountable
{
public:
typedef RefCount<CertValidationResponse> Pointer;
/**
* This class used to hold error informations returned from
* cert validator helper.
*/
class RecvdError
{
public:
RecvdError(): id(0), error_no(SSL_ERROR_NONE), cert(NULL), error_depth(-1) {}
RecvdError(const RecvdError &);
=== modified file 'src/ssl/helper.cc'
--- src/ssl/helper.cc 2017-01-01 00:12:22 +0000
+++ src/ssl/helper.cc 2017-01-12 13:05:40 +0000
@@ -219,41 +219,41 @@
if (!Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query.c_str(), item))
delete item;
}
delete crtdvdData;
}
void Ssl::CertValidationHelper::sslSubmit(Ssl::CertValidationRequest const &request, AsyncCall::Pointer &callback)
{
assert(ssl_crt_validator);
Ssl::CertValidationMsg message(Ssl::CrtdMessage::REQUEST);
message.setCode(Ssl::CertValidationMsg::code_cert_validate);
message.composeRequest(request);
debugs(83, 5, "SSL crtvd request: " << message.compose().c_str());
submitData *crtdvdData = new submitData;
crtdvdData->query = message.compose();
crtdvdData->query += '\n';
crtdvdData->callback = callback;
- crtdvdData->ssl.resetAndLock(request.ssl);
+ crtdvdData->ssl = request.ssl;
Ssl::CertValidationResponse::Pointer const*validationResponse;
if (CertValidationHelper::HelperCache &&
(validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query.c_str()))) {
CertValidationHelper::CbDialer *dialer = dynamic_cast<CertValidationHelper::CbDialer*>(callback->getDialer());
Must(dialer);
dialer->arg1 = *validationResponse;
ScheduleCallHere(callback);
delete crtdvdData;
return;
}
if (!ssl_crt_validator->trySubmit(crtdvdData->query.c_str(), sslCrtvdHandleReplyWrapper, crtdvdData)) {
Ssl::CertValidationResponse::Pointer resp = new Ssl::CertValidationResponse;;
resp->resultCode = ::Helper::BrokenHelper;
Ssl::CertValidationHelper::CbDialer *dialer = dynamic_cast<Ssl::CertValidationHelper::CbDialer*>(callback->getDialer());
Must(dialer);
dialer->arg1 = resp;
ScheduleCallHere(callback);
=== modified file 'src/ssl/support.cc'
--- src/ssl/support.cc 2017-01-01 00:12:22 +0000
+++ src/ssl/support.cc 2017-01-17 04:13:26 +0000
@@ -6,40 +6,41 @@
* Please see the COPYING and CONTRIBUTORS files for details.
*/
/* DEBUG: section 83 SSL accelerator support */
#include "squid.h"
/* MS Visual Studio Projects are monolithic, so we need the following
* #if to exclude the SSL code from compile process when not needed.
*/
#if USE_OPENSSL
#include "acl/FilledChecklist.h"
#include "anyp/PortCfg.h"
#include "fatal.h"
#include "fd.h"
#include "fde.h"
#include "globals.h"
#include "ipc/MemMap.h"
#include "security/CertError.h"
+#include "security/Session.h"
#include "SquidConfig.h"
#include "SquidTime.h"
#include "ssl/bio.h"
#include "ssl/Config.h"
#include "ssl/ErrorDetail.h"
#include "ssl/gadgets.h"
#include "ssl/support.h"
#include "URL.h"
#include <cerrno>
// TODO: Move ssl_ex_index_* global variables from global.cc here.
int ssl_ex_index_ssl_untrusted_chain = -1;
Ipc::MemMap *Ssl::SessionCache = NULL;
const char *Ssl::SessionCacheName = "ssl_session_cache";
static Ssl::CertsIndexedList SquidUntrustedCerts;
const EVP_MD *Ssl::DefaultSignHash = NULL;
@@ -632,47 +633,45 @@
debugs(83, 5, "Comparing private and public SSL keys.");
if (!SSL_CTX_check_private_key(ctx.get())) {
ssl_error = ERR_get_error();
debugs(83, DBG_CRITICAL, "ERROR: SSL private key '" << certfile << "' does not match public key '" <<
keyfile << "': " << Security::ErrorString(ssl_error));
return false;
}
*/
if (!configureSslContext(ctx, port)) {
debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context");
return false;
}
return true;
}
bool
-Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, long options, long fl)
+Ssl::InitClientContext(Security::ContextPointer &ctx, Security::PeerOptions &peer, long fl)
{
if (!ctx)
return false;
- SSL_CTX_set_options(ctx.get(), options);
-
maybeDisableRenegotiate(ctx);
if (!peer.sslCipher.isEmpty()) {
debugs(83, 5, "Using chiper suite " << peer.sslCipher << ".");
const char *cipher = peer.sslCipher.c_str();
if (!SSL_CTX_set_cipher_list(ctx.get(), cipher)) {
const int ssl_error = ERR_get_error();
fatalf("Failed to set SSL cipher suite '%s': %s\n",
cipher, Security::ErrorString(ssl_error));
}
}
if (!peer.certs.empty()) {
// TODO: support loading multiple cert/key pairs
auto &keys = peer.certs.front();
if (!keys.certFile.isEmpty()) {
debugs(83, DBG_IMPORTANT, "Using certificate in " << keys.certFile);
const char *certfile = keys.certFile.c_str();
@@ -699,89 +698,40 @@
fatalf("SSL private key '%s' does not match public key '%s': %s\n",
certfile, keyfile, Security::ErrorString(ssl_error));
}
}
}
maybeSetupRsaCallback(ctx);
if (fl & SSL_FLAG_DONT_VERIFY_PEER) {
debugs(83, 2, "NOTICE: Peer certificates are not verified for validity!");
SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_NONE, NULL);
} else {
debugs(83, 9, "Setting certificate verification callback.");
SSL_CTX_set_verify(ctx.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb);
}
return true;
}
/// \ingroup ServerProtocolSSLInternal
-int
-ssl_read_method(int fd, char *buf, int len)
-{
- auto ssl = fd_table[fd].ssl.get();
-
-#if DONT_DO_THIS
-
- if (!SSL_is_init_finished(ssl)) {
- errno = ENOTCONN;
- return -1;
- }
-
-#endif
-
- int i = SSL_read(ssl, buf, len);
- if (i > 0) {
- (void)VALGRIND_MAKE_MEM_DEFINED(buf, i);
- }
-
- if (i > 0 && SSL_pending(ssl) > 0) {
- debugs(83, 2, "SSL FD " << fd << " is pending");
- fd_table[fd].flags.read_pending = true;
- } else
- fd_table[fd].flags.read_pending = false;
-
- return i;
-}
-
-/// \ingroup ServerProtocolSSLInternal
-int
-ssl_write_method(int fd, const char *buf, int len)
-{
- auto ssl = fd_table[fd].ssl.get();
- if (!SSL_is_init_finished(ssl)) {
- errno = ENOTCONN;
- return -1;
- }
-
- int i = SSL_write(ssl, buf, len);
- return i;
-}
-
-void
-ssl_shutdown_method(SSL *ssl)
-{
- SSL_shutdown(ssl);
-}
-
-/// \ingroup ServerProtocolSSLInternal
static const char *
ssl_get_attribute(X509_NAME * name, const char *attribute_name)
{
static char buffer[1024];
buffer[0] = '\0';
if (strcmp(attribute_name, "DN") == 0) {
X509_NAME_oneline(name, buffer, sizeof(buffer));
} else {
int nid = OBJ_txt2nid(const_cast<char *>(attribute_name));
if (nid == 0) {
debugs(83, DBG_IMPORTANT, "WARNING: Unknown SSL attribute name '" << attribute_name << "'");
return nullptr;
}
X509_NAME_get_text_by_NID(name, nid, buffer, sizeof(buffer));
}
return *buffer ? buffer : nullptr;
}
@@ -1053,41 +1003,45 @@
if (!SSL_use_PrivateKey(ssl, pkey.get()))
return false;
return true;
}
bool
Ssl::verifySslCertificate(Security::ContextPointer &ctx, CertificateProperties const &properties)
{
// SSL_get_certificate is buggy in openssl versions 1.0.1d and 1.0.1e
// Try to retrieve certificate directly from Security::ContextPointer object
#if SQUID_USE_SSLGETCERTIFICATE_HACK
X509 ***pCert = (X509 ***)ctx->cert;
X509 * cert = pCert && *pCert ? **pCert : NULL;
#elif SQUID_SSLGETCERTIFICATE_BUGGY
X509 * cert = NULL;
assert(0);
#else
// Temporary ssl for getting X509 certificate from SSL_CTX.
- Security::SessionPointer ssl(SSL_new(ctx.get()));
+ Security::SessionPointer ssl(SSL_new(ctx.get()), [](SSL *p) {
+ debugs(83, 5, "SSL_free session=" << (void*)p);
+ SSL_free(p);
+ });
+ debugs(83, 5, "SSL_new session=" << (void*)ssl.get());
X509 * cert = SSL_get_certificate(ssl.get());
#endif
if (!cert)
return false;
ASN1_TIME * time_notBefore = X509_get_notBefore(cert);
ASN1_TIME * time_notAfter = X509_get_notAfter(cert);
bool ret = (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0);
if (!ret)
return false;
return certificateMatchesProperties(cert, properties);
}
bool
Ssl::setClientSNI(SSL *ssl, const char *fqdn)
{
//The SSL_CTRL_SET_TLSEXT_HOSTNAME is a openssl macro which indicates
// if the TLS servername extension (SNI) is enabled in openssl library.
#if defined(SSL_CTRL_SET_TLSEXT_HOSTNAME)
if (!SSL_set_tlsext_host_name(ssl, fqdn)) {
@@ -1436,87 +1390,40 @@
Ssl::CertificateProperties certProperties;
if (const char *cn = CommonHostName(cert.get())) {
certProperties.commonName = "Not trusted by \"";
certProperties.commonName += cn;
certProperties.commonName += "\"";
} else if (const char *org = getOrganization(cert.get())) {
certProperties.commonName = "Not trusted by \"";
certProperties.commonName += org;
certProperties.commonName += "\"";
} else
certProperties.commonName = "Not trusted";
certProperties.setCommonName = true;
// O, OU, and other CA subject fields will be mimicked
// Expiration date and other common properties will be mimicked
certProperties.signAlgorithm = Ssl::algSignSelf;
certProperties.signWithPkey.resetAndLock(pkey.get());
certProperties.mimicCert.resetAndLock(cert.get());
return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties);
}
-static bool
-SslCreate(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &conn, Ssl::Bio::Type type, const char *squidCtx)
-{
- if (!Comm::IsConnOpen(conn)) {
- debugs(83, DBG_IMPORTANT, "Gone connection");
- return false;
- }
-
- const char *errAction = NULL;
- int errCode = 0;
- if (auto ssl = SSL_new(ctx.get())) {
- const int fd = conn->fd;
- // without BIO, we would call SSL_set_fd(ssl, fd) instead
- if (BIO *bio = Ssl::Bio::Create(fd, type)) {
- Ssl::Bio::Link(ssl, bio); // cannot fail
-
- fd_table[fd].ssl.resetWithoutLocking(ssl);
- fd_table[fd].read_method = &ssl_read_method;
- fd_table[fd].write_method = &ssl_write_method;
- fd_note(fd, squidCtx);
- return true;
- }
- errCode = ERR_get_error();
- errAction = "failed to initialize I/O";
- SSL_free(ssl);
- } else {
- errCode = ERR_get_error();
- errAction = "failed to allocate handle";
- }
-
- debugs(83, DBG_IMPORTANT, "ERROR: " << squidCtx << ' ' << errAction <<
- ": " << Security::ErrorString(errCode));
- return false;
-}
-
-bool
-Ssl::CreateClient(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
-{
- return SslCreate(ctx, c, Ssl::Bio::BIO_TO_SERVER, squidCtx);
-}
-
-bool
-Ssl::CreateServer(const Security::ContextPointer &ctx, const Comm::ConnectionPointer &c, const char *squidCtx)
-{
- return SslCreate(ctx, c, Ssl::Bio::BIO_TO_CLIENT, squidCtx);
-}
-
static int
store_session_cb(SSL *ssl, SSL_SESSION *session)
{
if (!Ssl::SessionCache)
return 0;
debugs(83, 5, "Request to store SSL Session ");
SSL_SESSION_set_timeout(session, Config.SSL.session_ttl);
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
unsigned char *id = session->session_id;
unsigned int idlen = session->session_id_length;
#else
unsigned int idlen;
const unsigned char *id = SSL_SESSION_get_id(session, &idlen);
#endif
unsigned char key[MEMMAP_SLOT_KEY_SIZE];
// Session ids are of size 32bytes. They should always fit to a
// MemMap::Slot::key
=== modified file 'src/ssl/support.h'
--- src/ssl/support.h 2017-01-01 00:12:22 +0000
+++ src/ssl/support.h 2017-01-16 15:21:04 +0000
@@ -56,76 +56,59 @@
namespace AnyP
{
class PortCfg;
};
namespace Ipc
{
class MemMap;
}
namespace Ssl
{
/// initialize the SSL library global state.
/// call before generating any SSL context
void Initialize();
class ErrorDetail;
class CertValidationResponse;
typedef RefCount<CertValidationResponse> CertValidationResponsePointer;
-/// Creates SSL Client connection structure and initializes SSL I/O (Comm and BIO).
-/// On errors, emits DBG_IMPORTANT with details and returns false.
-bool CreateClient(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx);
-
-/// Creates SSL Server connection structure and initializes SSL I/O (Comm and BIO).
-/// On errors, emits DBG_IMPORTANT with details and returns false.
-bool CreateServer(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *squidCtx);
-
void SetSessionCallbacks(Security::ContextPointer &);
extern Ipc::MemMap *SessionCache;
extern const char *SessionCacheName;
/// initialize a TLS server context with OpenSSL specific settings
bool InitServerContext(Security::ContextPointer &, AnyP::PortCfg &);
/// initialize a TLS client context with OpenSSL specific settings
-bool InitClientContext(Security::ContextPointer &, Security::PeerOptions &, long options, long flags);
+bool InitClientContext(Security::ContextPointer &, Security::PeerOptions &, long flags);
#if defined(CRYPTO_LOCK_X509)
// portability wrapper for OpenSSL 1.0 vs 1.1
// use Security::CertPointer instead where possible
inline int X509_up_ref(X509 *t) {if (t) CRYPTO_add(&t->references, 1, CRYPTO_LOCK_X509); return 0;}
#endif
} //namespace Ssl
/// \ingroup ServerProtocolSSLAPI
-int ssl_read_method(int, char *, int);
-
-/// \ingroup ServerProtocolSSLAPI
-int ssl_write_method(int, const char *, int);
-
-/// \ingroup ServerProtocolSSLAPI
-void ssl_shutdown_method(SSL *ssl);
-
-/// \ingroup ServerProtocolSSLAPI
const char *sslGetUserEmail(SSL *ssl);
/// \ingroup ServerProtocolSSLAPI
const char *sslGetUserAttribute(SSL *ssl, const char *attribute_name);
/// \ingroup ServerProtocolSSLAPI
const char *sslGetCAAttribute(SSL *ssl, const char *attribute_name);
/// \ingroup ServerProtocolSSLAPI
const char *sslGetUserCertificatePEM(SSL *ssl);
/// \ingroup ServerProtocolSSLAPI
const char *sslGetUserCertificateChainPEM(SSL *ssl);
namespace Ssl
{
/// \ingroup ServerProtocolSSLAPI
typedef char const *GETX509ATTRIBUTE(X509 *, const char *);
/// \ingroup ServerProtocolSSLAPI
=== modified file 'src/tests/stub_libsecurity.cc'
--- src/tests/stub_libsecurity.cc 2017-01-01 00:12:22 +0000
+++ src/tests/stub_libsecurity.cc 2017-01-18 16:14:36 +0000
@@ -51,46 +51,57 @@
const char *PeerConnector::status() const STUB_RETVAL("")
void PeerConnector::commCloseHandler(const CommCloseCbParams &) STUB
void PeerConnector::connectionClosed(const char *) STUB
bool PeerConnector::prepareSocket() STUB_RETVAL(false)
void PeerConnector::setReadTimeout() STUB
bool PeerConnector::initialize(Security::SessionPointer &) STUB_RETVAL(false)
void PeerConnector::negotiate() STUB
bool PeerConnector::sslFinalized() STUB_RETVAL(false)
void PeerConnector::handleNegotiateError(const int) STUB
void PeerConnector::noteWantRead() STUB
void PeerConnector::noteWantWrite() STUB
void PeerConnector::noteNegotiationError(const int, const int, const int) STUB
// virtual Security::ContextPointer getTlsContext() = 0;
void PeerConnector::bail(ErrorState *) STUB
void PeerConnector::callBack() STUB
void PeerConnector::recordNegotiationDetails() STUB
}
#include "security/PeerOptions.h"
Security::PeerOptions Security::ProxyOutgoingConfig;
+Security::PeerOptions::PeerOptions() {
+#if !USE_GNUTLS
+ parsedOptions = 0;
+#endif
+ STUB_NOP
+}
+Security::PeerOptions::PeerOptions(const Security::PeerOptions &) {STUB}
+Security::PeerOptions &Security::PeerOptions::operator =(const Security::PeerOptions &) STUB_RETVAL(*this)
void Security::PeerOptions::parse(char const*) STUB
Security::ContextPointer Security::PeerOptions::createClientContext(bool) STUB_RETVAL(Security::ContextPointer())
void Security::PeerOptions::updateTlsVersionLimits() STUB
Security::ContextPointer Security::PeerOptions::createBlankContext() const STUB_RETVAL(Security::ContextPointer())
void Security::PeerOptions::updateContextCa(Security::ContextPointer &) STUB
void Security::PeerOptions::updateContextCrl(Security::ContextPointer &) STUB
+void Security::PeerOptions::updateSessionOptions(Security::SessionPointer &) STUB
void Security::PeerOptions::dumpCfg(Packable*, char const*) const STUB
-long Security::PeerOptions::parseOptions() STUB_RETVAL(0)
-long Security::PeerOptions::parseFlags() STUB_RETVAL(0)
+void Security::PeerOptions::parseOptions() STUB
void parse_securePeerOptions(Security::PeerOptions *) STUB
#include "security/ServerOptions.h"
//Security::ServerOptions::ServerOptions(const Security::ServerOptions &) STUB
void Security::ServerOptions::parse(const char *) STUB
void Security::ServerOptions::dumpCfg(Packable *, const char *) const STUB
Security::ContextPointer Security::ServerOptions::createBlankContext() const STUB_RETVAL(Security::ContextPointer())
bool Security::ServerOptions::createStaticServerContext(AnyP::PortCfg &) STUB_RETVAL(false)
void Security::ServerOptions::updateContextEecdh(Security::ContextPointer &) STUB
#include "security/Session.h"
namespace Security {
+bool CreateClientSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *) STUB_RETVAL(false)
+bool CreateServerSession(const Security::ContextPointer &, const Comm::ConnectionPointer &, const char *) STUB_RETVAL(false)
+void SessionSendGoodbye(const Security::SessionPointer &) STUB
bool SessionIsResumed(const Security::SessionPointer &) STUB_RETVAL(false)
void MaybeGetSessionResumeData(const Security::SessionPointer &, Security::SessionStatePointer &) STUB
void SetSessionResumeData(const Security::SessionPointer &, const Security::SessionStatePointer &) STUB
} // namespace Security
=== modified file 'src/tests/stub_libsslsquid.cc'
--- src/tests/stub_libsslsquid.cc 2017-01-01 00:12:22 +0000
+++ src/tests/stub_libsslsquid.cc 2017-01-12 13:05:40 +0000
@@ -34,45 +34,42 @@
//Ssl::CertificateStorageAction::CertificateStorageAction(const Mgr::Command::Pointer &cmd) STUB
Ssl::CertificateStorageAction::Pointer Ssl::CertificateStorageAction::Create(const Mgr::Command::Pointer &cmd) STUB_RETSTATREF(Ssl::CertificateStorageAction::Pointer)
void Ssl::CertificateStorageAction::dump(StoreEntry *sentry) STUB
void Ssl::GlobalContextStorage::addLocalStorage(Ip::Address const & address, size_t size_of_store) STUB
Ssl::LocalContextStorage *Ssl::GlobalContextStorage::getLocalStorage(Ip::Address const & address)
{ fatal(STUB_API " required"); static Ssl::LocalContextStorage v(0,0); return &v; }
void Ssl::GlobalContextStorage::reconfigureStart() STUB
//Ssl::GlobalContextStorage Ssl::TheGlobalContextStorage;
#include "ssl/ErrorDetail.h"
Security::ErrorCode parseErrorString(const char *name) STUB_RETVAL(0)
//const char *Ssl::getErrorName(Security::ErrorCode value) STUB_RETVAL(NULL)
Ssl::ErrorDetail::ErrorDetail(Security::ErrorCode, X509 *, X509 *, const char *) STUB
Ssl::ErrorDetail::ErrorDetail(ErrorDetail const &) STUB
const String & Ssl::ErrorDetail::toString() const STUB_RETSTATREF(String)
#include "ssl/support.h"
namespace Ssl
{
bool InitServerContext(Security::ContextPointer &, AnyP::PortCfg &) STUB_RETVAL(false)
-bool InitClientContext(Security::ContextPointer &, Security::PeerOptions &, long, const char *) STUB_RETVAL(false)
+bool InitClientContext(Security::ContextPointer &, Security::PeerOptions &, const char *) STUB_RETVAL(false)
} // namespace Ssl
-int ssl_read_method(int, char *, int) STUB_RETVAL(0)
-int ssl_write_method(int, const char *, int) STUB_RETVAL(0)
-void ssl_shutdown_method(SSL *ssl) STUB
const char *sslGetUserEmail(SSL *ssl) STUB_RETVAL(NULL)
const char *sslGetUserAttribute(SSL *ssl, const char *attribute_name) STUB_RETVAL(NULL)
const char *sslGetCAAttribute(SSL *ssl, const char *attribute_name) STUB_RETVAL(NULL)
const char *sslGetUserCertificatePEM(SSL *ssl) STUB_RETVAL(NULL)
const char *sslGetUserCertificateChainPEM(SSL *ssl) STUB_RETVAL(NULL)
namespace Ssl
{
//GETX509ATTRIBUTE GetX509UserAttribute;
//GETX509ATTRIBUTE GetX509CAAttribute;
//GETX509ATTRIBUTE GetX509Fingerprint;
std::vector<const char *> BumpModeStr = {""};
bool generateUntrustedCert(Security::CertPointer & untrustedCert, EVP_PKEY_Pointer & untrustedPkey, Security::CertPointer const & cert, EVP_PKEY_Pointer const & pkey) STUB_RETVAL(false)
Security::ContextPointer generateSslContext(CertificateProperties const &, AnyP::PortCfg &) STUB_RETVAL(Security::ContextPointer())
bool verifySslCertificate(Security::ContextPointer &, CertificateProperties const &) STUB_RETVAL(false)
Security::ContextPointer generateSslContextUsingPkeyAndCertFromMemory(const char *, AnyP::PortCfg &) STUB_RETVAL(Security::ContextPointer())
void addChainToSslContext(Security::ContextPointer &, STACK_OF(X509) *) STUB
void readCertChainAndPrivateKeyFromFiles(Security::CertPointer & cert, EVP_PKEY_Pointer & pkey, X509_STACK_Pointer & chain, char const * certFilename, char const * keyFilename) STUB
int matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data, ASN1_STRING *cn_data)) STUB_RETVAL(0)
bool checkX509ServerValidity(X509 *cert, const char *server) STUB_RETVAL(false)
int asn1timeToString(ASN1_TIME *tm, char *buf, int len) STUB_RETVAL(0)
=== modified file 'src/url.cc'
--- src/url.cc 2017-01-01 00:12:22 +0000
+++ src/url.cc 2017-01-14 07:26:47 +0000
@@ -813,41 +813,41 @@
break;
case AnyP::PROTO_FTP:
if (r->method == Http::METHOD_PUT)
rc = 1;
case AnyP::PROTO_GOPHER:
case AnyP::PROTO_WAIS:
case AnyP::PROTO_WHOIS:
if (r->method == Http::METHOD_GET)
rc = 1;
else if (r->method == Http::METHOD_HEAD)
rc = 1;
break;
case AnyP::PROTO_HTTPS:
-#if USE_OPENSSL
+#if USE_OPENSSL || USE_GNUTLS
rc = 1;
#else
/*
* Squid can't originate an SSL connection, so it should
* never receive an "https:" URL. It should always be
* CONNECT instead.
*/
rc = 0;
#endif
break;
default:
break;
}
return rc;
}
/*
* Quick-n-dirty host extraction from a URL. Steps:
More information about the squid-dev
mailing list