Skip to content

Commit

Permalink
fixup! 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 915d72d commit 54571d3
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 26 deletions.
16 changes: 11 additions & 5 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,13 @@ RemoteError::~RemoteError() {}

Finished::~Finished() {}

Connected::Connected(const std::string& peerName)
Connected::Connected(const std::string& peerName,
const epicsTime& time,
const std::shared_ptr<const pvxs::client::ServerCredentials> &cred)
:std::runtime_error("Connected")
,peerName(peerName)
,time(epicsTime::getCurrent())
,time(time)
,cred(cred)
{}

Connected::~Connected() {}
Expand Down Expand Up @@ -273,10 +276,13 @@ std::shared_ptr<Connect> ConnectBuilder::exec()
op->chan = Channel::build(context, op->_name, server);

bool cur = op->_connected = op->chan->state==Channel::Active;
if(cur && op->_onConn)
op->_onConn();
else if(!cur && op->_onDis)
if(cur && op->_onConn) {
auto& conn = op->chan->conn;
Connected evt(conn->peerName, conn->connTime, conn->cred);
op->_onConn(evt);
} else if(!cur && op->_onDis) {
op->_onDis();
}

op->chan->connectors.push_back(op.get());
});
Expand Down
31 changes: 30 additions & 1 deletion src/clientconn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,34 @@ void Connection::bevEvent(short events)

if(bev && (events&BEV_EVENT_CONNECTED)) {
log_debug_printf(io, "Connected to %s\n", peerName.c_str());
connTime = epicsTime::getCurrent();

auto peerCred(std::make_shared<ServerCredentials>());
peerCred->peer = peerName;
peerCred->isTLS = isTLS;

#ifdef PVXS_ENABLE_OPENSSL
if(isTLS) {
auto ctx = bufferevent_openssl_get_ssl(bev.get());
assert(ctx);
auto cert = SSL_get0_peer_certificate(ctx);
// SSL_get0_verified_chain()
if(cert) {
auto subj = X509_get_subject_name(cert);
char name[64];
if(subj && X509_NAME_get_text_by_NID(subj, NID_commonName, name, sizeof(name)-1)) {
name[sizeof(name)-1] = '\0';
log_debug_printf(io, "Peer CN=%s\n", name);
peerCred->method = "x509";
peerCred->account = name;
}
}
} else
#endif
{
peerCred->method = "anonymous";
}
cred = std::move(peerCred);

if(bufferevent_enable(bev.get(), EV_READ|EV_WRITE))
throw std::logic_error("Unable to enable BEV");
Expand Down Expand Up @@ -425,9 +453,10 @@ void Connection::handle_CREATE_CHANNEL()

auto conns(chan->connectors); // copy list

struct Connected connEvt(peerName, connTime, cred);
for(auto& conn : conns) {
if(!conn->_connected.exchange(true, std::memory_order_relaxed) && conn->_onConn)
conn->_onConn();
conn->_onConn(connEvt);
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/clientimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ struct Connection final : public ConnBase, public std::enable_shared_from_this<C

uint32_t nextIOID = 0x10002000u;

epicsTime connTime;
std::shared_ptr<const ServerCredentials> cred;

INST_COUNTER(Connection);

Connection(const std::shared_ptr<ContextImpl>& context,
Expand Down Expand Up @@ -159,7 +162,7 @@ struct ConnectImpl final : public Connect
std::shared_ptr<Channel> chan;
const std::string _name;
std::atomic<bool> _connected;
std::function<void()> _onConn;
std::function<void(const Connected&)> _onConn;
std::function<void()> _onDis;

ConnectImpl(const evbase& loop, const std::string& name)
Expand Down
4 changes: 3 additions & 1 deletion src/clientmon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ struct SubscriptionImpl final : public OperationBase, public Subscription
if(!maskConn) {
notify = queue.empty() && wantToNotify();

queue.emplace_back(std::make_exception_ptr(Connected(conn->peerName)));
queue.emplace_back(std::make_exception_ptr(Connected(conn->peerName,
conn->connTime,
conn->cred)));

log_debug_printf(io, "Server %s channel %s monitor PUSH Connected\n",
chan->conn ? chan->conn->peerName.c_str() : "<disconnected>",
Expand Down
17 changes: 8 additions & 9 deletions src/ossl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,22 +237,21 @@ ossl_setup_common(const SSL_METHOD *method, const impl::ConfigCommon &conf)
#endif

// TODO: SSL_CTX_set_options(), SSL_CTX_set_mode() ?
// TODO: CONF_modules_load_file(), SSL_CTX_config(ctx.ctx, "myiocname") ?

// we mandate TLS >= 1.3
(void)SSL_CTX_set_min_proto_version(ctx.ctx, TLS1_3_VERSION);
(void)SSL_CTX_set_max_proto_version(ctx.ctx, 0); // up to max.

// populate SSL_CTX::cert_store
for(auto& ca : conf.tls_authority_files) {
log_debug_printf(_setup, "Read TLS CAs from %s\n", ca.c_str());
if(1!=SSL_CTX_load_verify_locations(ctx.ctx, ca.c_str(), nullptr))
throw SSLError("oops");
for(auto& file : conf.tls_authority_files) {
log_debug_printf(_setup, "Read TLS CAs from %s\n", file.c_str());
if(1!=SSL_CTX_load_verify_locations(ctx.ctx, file.c_str(), nullptr))
throw SSLError("SSL_CTX_load_verify_locations");
}
for(auto& ca : conf.tls_authority_dirs) {
log_debug_printf(_setup, "Read TLS CAs in %s\n", ca.c_str());
if(1!=SSL_CTX_load_verify_locations(ctx.ctx, nullptr, ca.c_str()))
throw SSLError("oops");
for(auto& dir : conf.tls_authority_dirs) {
log_debug_printf(_setup, "Read TLS CAs in %s\n", dir.c_str());
if(1!=SSL_CTX_load_verify_locations(ctx.ctx, nullptr, dir.c_str()))
throw SSLError("SSL_CTX_load_verify_locations");
}

if(!conf.tls_keychain_file.empty()) {
Expand Down
45 changes: 41 additions & 4 deletions src/pvxs/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace client {

class Context;
struct Config;
struct ServerCredentials;

//! Operation failed because of connection to server was lost
struct PVXS_API Disconnect : public std::runtime_error
Expand All @@ -54,14 +55,25 @@ struct PVXS_API Finished : public Disconnect
virtual ~Finished();
};

//! For monitor only. Subscription has (re)connected.
//! Indication of connection to a server
struct PVXS_API Connected : public std::runtime_error
{
Connected(const std::string& peerName);
Connected(const std::string& peerName,
const epicsTime& time,
const std::shared_ptr<const ServerCredentials>& cred);
Connected(const std::string& peerName,
const std::shared_ptr<const ServerCredentials>& cred)
:Connected(peerName, epicsTime::getCurrent(), cred) // legacy
{}
virtual ~Connected();

//! Server IP address
const std::string peerName;
//! Local time of connection
const epicsTime time;
//! Identity of server.
//! @since UNRELEASED
const std::shared_ptr<const ServerCredentials> cred;
};

struct PVXS_API Interrupted : public std::runtime_error
Expand Down Expand Up @@ -610,6 +622,22 @@ class PVXS_API Context {
std::shared_ptr<Pvt> pvt;
};

//! Identity of a server
//! @since UNRELEASED
struct ServerCredentials {
//! Peer address (eg. numeric IPv4)
std::string peer;
//! The local interface address (eg. numeric IPv4) through which this client is connected.
//! May be a wildcard address (eg. 0.0.0.0) if the receiving socket is so bound.
std::string iface;
//! How account was authenticated. ("anonymous" or "x509")
std::string method;
//! Remote user account name. Meaning depends upon method.
std::string account;
//! Operation over secure transport
bool isTLS = false;
};

namespace detail {
struct PVRParser;

Expand Down Expand Up @@ -930,7 +958,7 @@ class ConnectBuilder
std::shared_ptr<Context::Pvt> ctx;
std::string _pvname;
std::string _server;
std::function<void()> _onConn;
std::function<void(const Connected&)> _onConn;
std::function<void()> _onDis;
bool _syncCancel = true;
public:
Expand All @@ -941,7 +969,16 @@ class ConnectBuilder
{}

//! Handler to be invoked when channel becomes connected.
ConnectBuilder& onConnect(std::function<void()>&& cb) { _onConn = std::move(cb); return *this; }
//! @since UNRELEASED
ConnectBuilder& onConnect(std::function<void(const Connected&)>&& cb)
{ _onConn = std::move(cb); return *this; }
//! Handler to be invoked when channel becomes connected.
//! @since UNRELEASED Prefer void(const Connected&) in new code.
ConnectBuilder& onConnect(std::function<void()>&& cb)
{
_onConn = [cb](const Connected&) { cb(); };
return *this;
}
//! Handler to be invoked when channel becomes disconnected.
ConnectBuilder& onDisconnect(std::function<void()>&& cb) { _onDis = std::move(cb); return *this; }

Expand Down
6 changes: 5 additions & 1 deletion src/pvxs/srvcommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct PVXS_API ClientCredentials {
//! The local interface address (eg. numeric IPv4) through which this client is connected.
//! May be a wildcard address (eg. 0.0.0.0) if the receiving socket is so bound.
std::string iface;
//! Authentication "method"
//! How account was authenticated. ("anonymous", "ca", or "x509")
std::string method;
//! Remote user account name. Meaning depends upon method.
std::string account;
Expand All @@ -47,6 +47,10 @@ struct PVXS_API ClientCredentials {
* On other targets, an empty list is returned.
*/
std::set<std::string> roles() const;
/** Operation over secure transport
* @since UNRELEASED
*/
bool isTLS = false;
};

PVXS_API
Expand Down
3 changes: 2 additions & 1 deletion src/serverconn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ void ServerConn::handle_CONNECTION_VALIDATION()
std::string(SB()<<auth).c_str());

auto C(std::make_shared<server::ClientCredentials>(*cred));
C->isTLS = iface->isTLS;

if(selected=="ca") {
auth["user"].as<std::string>([&C, &selected](const std::string& user) {
Expand All @@ -230,7 +231,7 @@ void ServerConn::handle_CONNECTION_VALIDATION()
auto ctx = bufferevent_openssl_get_ssl(bev.get());
assert(ctx);
auto cert = SSL_get0_peer_certificate(ctx);
// SSL_get_peer_cert_chain()
// SSL_get0_verified_chain()
if(cert) {
auto subj = X509_get_subject_name(cert);
char name[64];
Expand Down
87 changes: 84 additions & 3 deletions test/testtls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ void testClientReconfig() {
testShow()<<__func__;

auto serv_conf(server::Config::isolated());
serv_conf.tls_keychain_file = "../O.Common/server1.p12";
serv_conf.tls_keychain_file = "../O.Common/ioc1.p12";

auto serv(serv_conf.build()
.addSource("whoami", std::make_shared<WhoAmI>()));
Expand All @@ -149,13 +149,24 @@ void testClientReconfig() {

epicsEvent evt;
auto sub(cli.monitor("whoami")
.maskConnected(true)
.maskConnected(false)
.maskDisconnected(false)
.event([&evt](client::Subscription&) {
evt.signal();
}).exec());
Value update;

try {
pop(sub, evt);
testFail("Unexpected success");
testSkip(2, "oops");
} catch(client::Connected& e) {
testTrue(e.cred->isTLS);
testEq(e.cred->method, "x509");
testEq(e.cred->account, "ioc1");
}
testDiag("Connect");

update = pop(sub, evt);
testEq(update["value"].as<std::string>(), "x509/client1");

Expand All @@ -169,20 +180,90 @@ void testClientReconfig() {
});
testDiag("Disconnect");

testThrows<client::Connected>([&sub, &evt]{
pop(sub, evt);
});
testDiag("Reconnect");

update = pop(sub, evt);
testEq(update["value"].as<std::string>(), "x509/client2");
}

void testServerReconfig() {
testShow()<<__func__;

auto serv_conf(server::Config::isolated());
serv_conf.tls_keychain_file = "../O.Common/server1.p12";

auto serv(serv_conf.build()
.addSource("whoami", std::make_shared<WhoAmI>()));

auto cli_conf(serv.clientConfig());
cli_conf.tls_keychain_file = "../O.Common/ioc1.p12";

auto cli(cli_conf.build());

serv.start();

epicsEvent evt;
auto sub(cli.monitor("whoami")
.maskConnected(false)
.maskDisconnected(false)
.event([&evt](client::Subscription&) {
evt.signal();
}).exec());
Value update;

try {
pop(sub, evt);
testFail("Unexpected success");
testSkip(2, "oops");
} catch(client::Connected& e) {
testTrue(e.cred->isTLS);
testEq(e.cred->method, "x509");
testEq(e.cred->account, "server1");
}
testDiag("Connect");

update = pop(sub, evt);
testEq(update["value"].as<std::string>(), "x509/ioc1");

serv_conf = serv.config();
serv_conf.tls_keychain_file = "../O.Common/ioc1.p12";
testDiag("serv.reconfigure()");
serv.reconfigure(serv_conf);

testThrows<client::Disconnect>([&sub, &evt]{
pop(sub, evt);
});
testDiag("Disconnect");

try {
pop(sub, evt);
testFail("Unexpected success");
testSkip(2, "oops");
} catch(client::Connected& e) {
testTrue(e.cred->isTLS);
testEq(e.cred->method, "x509");
testEq(e.cred->account, "ioc1");
}
testDiag("Reconnect");

update = pop(sub, evt);
testEq(update["value"].as<std::string>(), "x509/ioc1");
}

} // namespace

MAIN(testtls)
{
testPlan(0);
testPlan(18);
testSetup();
logger_config_env();
testGetSuper();
testGetIntermediate();
testClientReconfig();
testServerReconfig();
cleanup_for_valgrind();
return testDone();
}

0 comments on commit 54571d3

Please sign in to comment.