Skip to content

Commit

Permalink
Verify server certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
badaix committed Jan 25, 2025
1 parent 3d5744c commit b20bd90
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 19 deletions.
21 changes: 21 additions & 0 deletions client/client_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,27 @@ void ClientConnectionWs::write(boost::asio::streambuf& buffer, WriteHandler&& wr
ClientConnectionWss::ClientConnectionWss(boost::asio::io_context& io_context, boost::asio::ssl::context& ssl_context, ClientSettings::Server server)
: ClientConnection(io_context, std::move(server)), ssl_ws_(strand_, ssl_context)
{
if (server.certificate.has_value())
{
ssl_ws_.next_layer().set_verify_mode(boost::asio::ssl::verify_peer);
ssl_ws_.next_layer().set_verify_callback([](bool preverified, boost::asio::ssl::verify_context& ctx)
{
// The verify callback can be used to check whether the certificate that is
// being presented is valid for the peer. For example, RFC 2818 describes
// the steps involved in doing this for HTTPS. Consult the OpenSSL
// documentation for more details. Note that the callback is called once
// for each certificate in the certificate chain, starting from the root
// certificate authority.

// In this example we will simply print the certificate's subject name.
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
LOG(INFO, LOG_TAG) << "verifying cert: '" << subject_name << "', pre verified: " << preverified << "\n";

return preverified;
});
}
}


Expand Down
9 changes: 9 additions & 0 deletions client/client_settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include "player/pcm_device.hpp"

// standard headers
#include <filesystem>
#include <optional>
#include <string>


Expand Down Expand Up @@ -64,6 +66,13 @@ struct ClientSettings
std::string protocol;
/// server port
size_t port{1704};
/// server certificate
std::optional<std::filesystem::path> certificate;
/// Is ssl in use?
bool isSsl() const
{
return (protocol == "wss");
}
};

/// The audio player (DAC)
Expand Down
15 changes: 13 additions & 2 deletions client/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,19 @@ Controller::Controller(boost::asio::io_context& io_context, const ClientSettings
: io_context_(io_context), ssl_context_(boost::asio::ssl::context::tlsv12_client), timer_(io_context), settings_(settings), stream_(nullptr),
decoder_(nullptr), player_(nullptr), serverSettings_(nullptr)
{
// TODO: Load and verify certificate
// ssl_context_.load_verify_file("/home/johannes/Develop/snapcast/server/etc/certs/snapcastCA.crt");
if (settings.server.isSsl() && settings.server.certificate.has_value())
{
boost::system::error_code ec;
ssl_context_.set_default_verify_paths(ec);
if (ec.failed())
LOG(WARNING, LOG_TAG) << "Failed to load system certificates: " << ec << "\n";
if (!settings.server.certificate->empty())
{
ssl_context_.load_verify_file(settings.server.certificate.value(), ec);
if (ec.failed())
throw SnapException("Failed to load certificate: " + settings.server.certificate.value().native() + ": " + ec.message());
}
}
}


Expand Down
47 changes: 32 additions & 15 deletions client/snapclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include <boost/asio/signal_set.hpp>

// standard headers
#include <filesystem>
#include <iostream>
#ifndef WINDOWS
#include <csignal>
Expand Down Expand Up @@ -135,23 +136,27 @@ int main(int argc, char** argv)
ClientSettings settings;
string pcm_device(player::DEFAULT_DEVICE);

OptionParser op("Allowed options");
auto helpSwitch = op.add<Switch>("", "help", "produce help message");
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "produce groff message");
auto versionSwitch = op.add<Switch>("v", "version", "show version number");
op.add<Value<string>>("h", "host", "server hostname or ip address", "", &settings.server.host);
op.add<Value<size_t>>("p", "port", "server port", 1704, &settings.server.port);
op.add<Value<size_t>>("i", "instance", "instance id when running multiple instances on the same host", 1, &settings.instance);
op.add<Value<string>>("", "hostID", "unique host id, default is MAC address", "", &settings.host_id);
OptionParser op("Usage: snapclient [options...] [url]\n\n"
" With 'url' = <tcp|ws|wss>://<snapserver host or IP>[:port]\n"
" For example: \"tcp:\\\\192.168.1.1:1704\", or \"wss:\\\\homeserver.local\"\n"
" If 'url' is not configured, snapclient tries to resolve the snapserver IP via mDNS\n");
auto helpSwitch = op.add<Switch>("", "help", "Produce help message");
auto groffSwitch = op.add<Switch, Attribute::hidden>("", "groff", "Produce groff message");
auto versionSwitch = op.add<Switch>("v", "version", "Show version number");
op.add<Value<string>>("h", "host", "(deprecated, use [url]) Server hostname or ip address", "", &settings.server.host);
op.add<Value<size_t>>("p", "port", "(deprecated, use [url]) Server port", 1704, &settings.server.port);
op.add<Value<size_t>>("i", "instance", "Instance id when running multiple instances on the same host", 1, &settings.instance);
op.add<Value<string>>("", "hostID", "Unique host id, default is MAC address", "", &settings.host_id);
auto server_cert_opt = op.add<Implicit<std::filesystem::path>>("", "server-cert", "Verify server with certificate", "default certificates");

// PCM device specific
#if defined(HAS_ALSA) || defined(HAS_PULSE) || defined(HAS_WASAPI)
auto listSwitch = op.add<Switch>("l", "list", "list PCM devices");
/*auto soundcardValue =*/op.add<Value<string>>("s", "soundcard", "index or name of the pcm device", pcm_device, &pcm_device);
auto listSwitch = op.add<Switch>("l", "list", "List PCM devices");
/*auto soundcardValue =*/op.add<Value<string>>("s", "Soundcard", "index or name of the pcm device", pcm_device, &pcm_device);
#endif
/*auto latencyValue =*/op.add<Value<int>>("", "latency", "latency of the PCM device", 0, &settings.player.latency);
/*auto latencyValue =*/op.add<Value<int>>("", "Latency", "latency of the PCM device", 0, &settings.player.latency);
#ifdef HAS_SOXR
auto sample_format = op.add<Value<string>>("", "sampleformat", "resample audio stream to <rate>:<bits>:<channels>", "");
auto sample_format = op.add<Value<string>>("", "sampleformat", "Resample audio stream to <rate>:<bits>:<channels>", "");
#endif

auto supported_players = Controller::getSupportedPlayerNames();
Expand All @@ -162,7 +167,7 @@ int main(int argc, char** argv)

// sharing mode
#if defined(HAS_OBOE) || defined(HAS_WASAPI)
auto sharing_mode = op.add<Value<string>>("", "sharingmode", "audio mode to use [shared|exclusive]", "shared");
auto sharing_mode = op.add<Value<string>>("", "sharingmode", "Audio mode to use [shared|exclusive]", "shared");
#endif

// mixer
Expand All @@ -183,12 +188,12 @@ int main(int argc, char** argv)
// daemon settings
#ifdef HAS_DAEMON
int processPriority(-3);
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "daemonize, optional process priority [-20..19]", processPriority, &processPriority);
auto daemonOption = op.add<Implicit<int>>("d", "daemon", "Daemonize, optional process priority [-20..19]", processPriority, &processPriority);
auto userValue = op.add<Value<string>>("", "user", "the user[:group] to run snapclient as when daemonized");
#endif

// logging
op.add<Value<string>>("", "logsink", "log sink [null,system,stdout,stderr,file:<filename>]", settings.logging.sink, &settings.logging.sink);
op.add<Value<string>>("", "logsink", "Log sink [null,system,stdout,stderr,file:<filename>]", settings.logging.sink, &settings.logging.sink);
auto logfilterOption = op.add<Value<string>>(
"", "logfilter", "log filter <tag>:<level>[,<tag>:<level>]* with tag = * or <log tag> and level = [trace,debug,info,notice,warning,error,fatal]",
settings.logging.filter);
Expand Down Expand Up @@ -318,6 +323,18 @@ int main(int argc, char** argv)
settings.server.port = 1788;
}

if (server_cert_opt->is_set())
{
if (server_cert_opt->get_default() == server_cert_opt->value())
settings.server.certificate = "";
else
settings.server.certificate = std::filesystem::weakly_canonical(server_cert_opt->value());
if (settings.server.certificate.value_or("").empty())
LOG(INFO, LOG_TAG) << "Server certificate: default certificates\n";
else
LOG(INFO, LOG_TAG) << "Server certificate: " << settings.server.certificate.value_or("") << "\n";
}

#if !defined(HAS_AVAHI) && !defined(HAS_BONJOUR)
if (settings.server.host.empty())
throw SnapException("Snapserver host not configured and mDNS not available, please configure with \"--host\".");
Expand Down
4 changes: 2 additions & 2 deletions common/popl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
( _ \ / \( _ \( )
) __/( O )) __// (_/\
(__) \__/(__) \____/
version 1.3.0
version 1.3.1
https://github.com/badaix/popl
This file is part of popl (program options parser lib)
Expand Down Expand Up @@ -1167,7 +1167,7 @@ inline std::string ConsoleOptionPrinter::print(const Attribute& max_attribute) c

std::stringstream s;
if (!option_parser_->description().empty())
s << option_parser_->description() << ":\n";
s << option_parser_->description() << "\n";

size_t optionRightMargin(20);
const size_t maxDescriptionLeftMargin(40);
Expand Down

0 comments on commit b20bd90

Please sign in to comment.