Skip to content

Commit

Permalink
Add TLS support w/ OpenSSL
Browse files Browse the repository at this point in the history
  • Loading branch information
mdavidsaver committed Jul 26, 2023
1 parent 28cc210 commit 0cc14d9
Show file tree
Hide file tree
Showing 26 changed files with 1,420 additions and 109 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci-scripts-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ jobs:
- name: "apt-get install"
run: |
sudo apt-get update
sudo apt-get -y install g++-mingw-w64-x86-64 cmake gdb qemu-system-x86 libssl-dev
sudo apt-get -y install g++-mingw-w64-x86-64 cmake gdb qemu-system-x86 libssl-dev \
python3-openssl python-is-python3
if: runner.os == 'Linux'
- name: Host Info
run: openssl version -a
Expand Down
10 changes: 10 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ ifeq (YES,$(EVENT2_HAS_OPENSSL))
USR_CPPFLAGS += -DPVXS_ENABLE_OPENSSL
endif

# set to NO to disable handling of $SSLKEYLOGFILE
PVXS_ENABLE_SSLKEYLOGFILE ?= YES

PVXS_ENABLE_SSLKEYLOGFILE_YES = -DPVXS_ENABLE_SSLKEYLOGFILE
USR_CPPFLAGS += $(PVXS_ENABLE_SSLKEYLOGFILE_$(PVXS_ENABLE_SSLKEYLOGFILE))

ifdef T_A
ifneq ($(CONFIG_LOADED),YES)
$(error Toolchain inspection failed $(MAKEFILE_LIST))
Expand Down Expand Up @@ -111,6 +117,10 @@ LIB_SRCS += clientget.cpp
LIB_SRCS += clientmon.cpp
LIB_SRCS += clientdiscover.cpp

ifeq (YES,$(EVENT2_HAS_OPENSSL))
LIB_SRCS += ossl.cpp
endif

LIB_LIBS += Com

# special case matching configure/RULES_PVXS_MODULE
Expand Down
79 changes: 72 additions & 7 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ void Channel::disconnect(const std::shared_ptr<Channel>& self)
name.c_str());

} else if(context->state==ContextImpl::Running) { // reconnect to specific server
conn = Connection::build(context, forcedServer, true);
conn = Connection::build(context, forcedServer, true, false); // TODO: how to TLS?

conn->pending[cid] = self;
state = Connecting;
Expand Down Expand Up @@ -380,7 +380,7 @@ std::shared_ptr<Channel> Channel::build(const std::shared_ptr<ContextImpl>& cont

} else { // bypass search and connect so a specific server
chan->forcedServer = forceServer;
chan->conn = Connection::build(context, forceServer);
chan->conn = Connection::build(context, forceServer, false, false); // TODO: how to TLS?

chan->conn->pending[chan->cid] = chan;
chan->state = Connecting;
Expand Down Expand Up @@ -410,6 +410,45 @@ Context::Context(const Config& conf)

Context::~Context() {}

void Context::reconfigure(const Config& newconf)
{
if(!pvt)
throw std::logic_error("NULL Context");

#ifdef PVXS_ENABLE_OPENSSL

ossl::SSLContext new_context;
if(!newconf.tls_keychain_file.empty()
|| !newconf.tls_authority_files.empty()
|| !newconf.tls_authority_dirs.empty())
{
new_context = ossl::SSLContext::for_client(newconf);
}

pvt->impl->manager.loop().call([this, &new_context](){

log_debug_printf(setup, "Client reconfigure%s", "\n");

auto conns(std::move(pvt->impl->connByAddr));

for(auto& pair : conns) {
auto conn = pair.second.lock();
if(!conn)
continue;

conn->cleanup();
}

conns.clear();

pvt->impl->tls_context = new_context;
});

#else
pvt->impl->manager.loop().sync();
#endif
}

const Config& Context::config() const
{
if(!pvt)
Expand Down Expand Up @@ -542,6 +581,15 @@ ContextImpl::ContextImpl(const Config& conf, const evbase& tcp_loop)
,nsChecker(__FILE__, __LINE__,
event_new(tcp_loop.base, -1, EV_TIMEOUT|EV_PERSIST, &ContextImpl::onNSCheckS, this))
{
#ifdef PVXS_ENABLE_OPENSSL
if(!effective.tls_keychain_file.empty()
|| !effective.tls_authority_files.empty()
|| !effective.tls_authority_dirs.empty())
{
tls_context = ossl::SSLContext::for_client(effective);
}
#endif

searchBuckets.resize(nBuckets);

std::set<SockAddr, SockAddrOnlyLess> bcasts;
Expand Down Expand Up @@ -654,9 +702,10 @@ void ContextImpl::startNS()
// start connections to name servers
for(auto& ns : nameServers) {
const auto& serv = ns.first;
ns.second = Connection::build(shared_from_this(), serv);
ns.second = Connection::build(shared_from_this(), serv, false, false); // TODO: how to TLS?
ns.second->nameserver = true;
log_debug_printf(io, "Connecting to nameserver %s\n", ns.second->peerName.c_str());
log_debug_printf(io, "Connecting to nameserver %s%s\n",
ns.second->peerName.c_str(), ns.second->isTLS ? " TLS" : "");
}

if(event_add(nsChecker.get(), &tcpNSCheckInterval))
Expand Down Expand Up @@ -873,7 +922,16 @@ void procSearchReply(ContextImpl& self, const SockAddr& src, uint8_t peerVersion
self.onBeacon(fakebeacon);
}

if(!found || proto!="tcp")
bool isTCP = proto=="tcp";

#ifdef PVXS_ENABLE_OPENSSL
bool isTLS = proto=="tls";
if(!self.tls_context && isTLS)
return;
#else
const bool isTLS = false;
#endif
if(!found || !(isTCP || isTLS))
return;

for(auto n : range(nSearch)) {
Expand Down Expand Up @@ -901,7 +959,7 @@ void procSearchReply(ContextImpl& self, const SockAddr& src, uint8_t peerVersion
chan->guid = guid;
chan->replyAddr = serv;

chan->conn = Connection::build(self.shared_from_this(), serv);
chan->conn = Connection::build(self.shared_from_this(), serv, false, isTLS);

chan->conn->pending[chan->cid] = chan;
chan->state = Channel::Connecting;
Expand Down Expand Up @@ -1061,6 +1119,13 @@ void ContextImpl::tickSearch(SearchKind kind, bool poked)
if(kind == SearchKind::discover) {
to_wire(M, uint8_t(0u));

#ifdef PVXS_ENABLE_OPENSSL
} else if(tls_context) {
to_wire(M, uint8_t(2u));
to_wire(M, "tls");
to_wire(M, "tcp");
#endif

} else {
to_wire(M, uint8_t(1u));
to_wire(M, "tcp");
Expand Down Expand Up @@ -1286,7 +1351,7 @@ void ContextImpl::onNSCheck()
if(ns.second && ns.second->state != ConnBase::Disconnected) // hold-off, connecting, or connected
continue;

ns.second = Connection::build(shared_from_this(), ns.first);
ns.second = Connection::build(shared_from_this(), ns.first, false, false); // How to TLS?
ns.second->nameserver = true;
log_debug_printf(io, "Reconnecting nameserver %s\n", ns.second->peerName.c_str());
}
Expand Down
64 changes: 53 additions & 11 deletions src/clientconn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ DEFINE_LOGGER(remote, "pvxs.remote.log");

Connection::Connection(const std::shared_ptr<ContextImpl>& context,
const SockAddr& peerAddr,
bool reconn)
bool reconn,
bool isTLS)
:ConnBase (true, context->effective.sendBE(),
nullptr,
peerAddr)
,context(context)
,isTLS(isTLS)
,echoTimer(__FILE__, __LINE__,
event_new(context->tcp_loop.base, -1, EV_TIMEOUT|EV_PERSIST, &tickEchoS, this))
{
Expand All @@ -45,15 +47,16 @@ Connection::~Connection()
}

std::shared_ptr<Connection> Connection::build(const std::shared_ptr<ContextImpl>& context,
const SockAddr& serv, bool reconn)
const SockAddr& serv, bool reconn, bool tls)
{
if(context->state!=ContextImpl::Running)
throw std::logic_error("Context close()d");

auto pair(std::make_pair(serv, tls));
std::shared_ptr<Connection> ret;
auto it = context->connByAddr.find(serv);
auto it = context->connByAddr.find(pair);
if(it==context->connByAddr.end() || !(ret = it->second.lock())) {
context->connByAddr[serv] = ret = std::make_shared<Connection>(context, serv, reconn);
context->connByAddr[pair] = ret = std::make_shared<Connection>(context, serv, reconn, tls);
}
return ret;
}
Expand All @@ -62,19 +65,46 @@ void Connection::startConnecting()
{
assert(!this->bev);

auto bev(bufferevent_socket_new(context->tcp_loop.base, -1, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS));
evbufferevent bev;

bufferevent_setcb(bev, &bevReadS, nullptr, &bevEventS, this);
#ifdef PVXS_ENABLE_OPENSSL
if(isTLS) {
auto ctx(SSL_new(context->tls_context.ctx));
if(!ctx)
throw ossl::SSLError("SSL_new");

// w/ BEV_OPT_CLOSE_ON_FREE calls SSL_free() on error
bev.reset(bufferevent_openssl_socket_new(context->tcp_loop.base,
-1,
ctx,
BUFFEREVENT_SSL_CONNECTING,
BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS));

// added with libevent 2.2.1-alpha
//(void)bufferevent_ssl_set_flags(bev.get(), BUFFEREVENT_SSL_DIRTY_SHUTDOWN);
// deprecated, but not yet removed
bufferevent_openssl_set_allow_dirty_shutdown(bev.get(), 1);

} else
#endif
{
bev.reset(bufferevent_socket_new(context->tcp_loop.base,
-1,
BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS));
}

bufferevent_setcb(bev.get(), &bevReadS, nullptr, &bevEventS, this);

timeval tmo(totv(context->effective.tcpTimeout));
bufferevent_set_timeouts(bev, &tmo, &tmo);
bufferevent_set_timeouts(bev.get(), &tmo, &tmo);

if(bufferevent_socket_connect(bev, const_cast<sockaddr*>(&peerAddr->sa), peerAddr.size()))
if(bufferevent_socket_connect(bev.get(), const_cast<sockaddr*>(&peerAddr->sa), peerAddr.size()))
throw std::runtime_error("Unable to begin connecting");

connect(bev);
connect(std::move(bev));

log_debug_printf(io, "Connecting to %s, RX readahead %zu\n", peerName.c_str(), readahead);
log_debug_printf(io, "Connecting to %s, RX readahead %zu%s\n",
peerName.c_str(), readahead, isTLS ? " TLS" : "");
}

void Connection::createChannels()
Expand Down Expand Up @@ -128,6 +158,14 @@ void Connection::sendDestroyRequest(uint32_t sid, uint32_t ioid)

void Connection::bevEvent(short events)
{
#ifdef PVXS_ENABLE_OPENSSL
if((events & BEV_EVENT_ERROR) && isTLS && bev) {
while(auto err = bufferevent_get_openssl_error(bev.get())) {
log_err_printf(io, "TLS Error (0x%lx) %s\n",
err, ERR_reason_error_string(err));
}
}
#endif
ConnBase::bevEvent(events);
// called Connection::cleanup()

Expand Down Expand Up @@ -157,7 +195,7 @@ void Connection::cleanup()
{
ready = false;

context->connByAddr.erase(peerAddr);
context->connByAddr.erase(std::make_pair(peerAddr, isTLS));

if(bev)
bev.reset();
Expand Down Expand Up @@ -220,6 +258,10 @@ void Connection::handle_CONNECTION_VALIDATION()

if(method=="ca" || (method=="anonymous" && selected!="ca"))
selected = method;
#ifdef PVXS_ENABLE_OPENSSL
else if(isTLS && method=="x509" && context->tls_context.have_certificate())
selected = method;
#endif
}

if(!M.good()) {
Expand Down
13 changes: 10 additions & 3 deletions src/clientimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ struct RequestInfo {

struct Connection final : public ConnBase, public std::enable_shared_from_this<Connection> {
const std::shared_ptr<ContextImpl> context;
const bool isTLS;

// While HoldOff, the time until re-connection
// While Connected, periodic Echo
Expand All @@ -106,13 +107,14 @@ struct Connection final : public ConnBase, public std::enable_shared_from_this<C

Connection(const std::shared_ptr<ContextImpl>& context,
const SockAddr &peerAddr,
bool reconn);
bool reconn, bool isTLS);
virtual ~Connection();

static
std::shared_ptr<Connection> build(const std::shared_ptr<ContextImpl>& context,
const SockAddr& serv,
bool reconn=false);
bool reconn,
bool isTLS);

private:
void startConnecting();
Expand Down Expand Up @@ -304,7 +306,8 @@ struct ContextImpl : public std::enable_shared_from_this<ContextImpl>
// chanByName key'd by (pv, forceServer)
std::map<std::pair<std::string, std::string>, std::shared_ptr<Channel>> chanByName;

std::map<SockAddr, std::weak_ptr<Connection>> connByAddr;
// pair (addr, useTLS)
std::map<std::pair<SockAddr, bool>, std::weak_ptr<Connection>> connByAddr;

std::vector<std::pair<SockAddr, std::shared_ptr<Connection>>> nameServers;

Expand All @@ -323,6 +326,10 @@ struct ContextImpl : public std::enable_shared_from_this<ContextImpl>
const evevent cacheCleaner;
const evevent nsChecker;

#ifdef PVXS_ENABLE_OPENSSL
ossl::SSLContext tls_context;
#endif

INST_COUNTER(ClientContextImpl);

ContextImpl(const Config& conf, const evbase &tcp_loop);
Expand Down
Loading

0 comments on commit 0cc14d9

Please sign in to comment.