Implement permission management for nvhttp
This commit is contained in:
15
src/crypto.h
15
src/crypto.h
@@ -34,10 +34,25 @@ namespace crypto {
|
|||||||
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
|
using pkey_ctx_t = util::safe_ptr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
|
||||||
using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
|
using bignum_t = util::safe_ptr<BIGNUM, BN_free>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The permissions of a client.
|
||||||
|
*/
|
||||||
|
enum class PERM: uint32_t {
|
||||||
|
list = 1 << 0, // Allow list apps
|
||||||
|
view = 1 << 3, // Allow view streams
|
||||||
|
input = 1 << 7, // Allow any input: kbd/mouse, controller
|
||||||
|
server_cmd = 1 << 10, // Allow execute server cmd
|
||||||
|
launch = 1 << 15, // Allow launch apps
|
||||||
|
_default = view | list, // Default permissions for new clients
|
||||||
|
_all = launch | server_cmd | input | view | list, // All current permissions
|
||||||
|
_no = 0, // No permissions are granted
|
||||||
|
};
|
||||||
|
|
||||||
struct named_cert_t {
|
struct named_cert_t {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string uuid;
|
std::string uuid;
|
||||||
std::string cert;
|
std::string cert;
|
||||||
|
PERM perm;
|
||||||
};
|
};
|
||||||
|
|
||||||
using p_named_cert_t = std::shared_ptr<named_cert_t>;
|
using p_named_cert_t = std::shared_ptr<named_cert_t>;
|
||||||
|
|||||||
148
src/nvhttp.cpp
148
src/nvhttp.cpp
@@ -47,6 +47,7 @@ namespace nvhttp {
|
|||||||
namespace pt = boost::property_tree;
|
namespace pt = boost::property_tree;
|
||||||
|
|
||||||
using p_named_cert_t = crypto::p_named_cert_t;
|
using p_named_cert_t = crypto::p_named_cert_t;
|
||||||
|
using PERM = crypto::PERM;
|
||||||
|
|
||||||
struct client_t {
|
struct client_t {
|
||||||
std::vector<p_named_cert_t> named_devices;
|
std::vector<p_named_cert_t> named_devices;
|
||||||
@@ -242,6 +243,7 @@ namespace nvhttp {
|
|||||||
named_cert_node.put("name"s, final_name);
|
named_cert_node.put("name"s, final_name);
|
||||||
named_cert_node.put("cert"s, named_cert_p->cert);
|
named_cert_node.put("cert"s, named_cert_p->cert);
|
||||||
named_cert_node.put("uuid"s, named_cert_p->uuid);
|
named_cert_node.put("uuid"s, named_cert_p->uuid);
|
||||||
|
named_cert_node.put("perm"s, (uint32_t)named_cert_p->perm);
|
||||||
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,6 +301,7 @@ namespace nvhttp {
|
|||||||
named_cert_p->name = ""s;
|
named_cert_p->name = ""s;
|
||||||
named_cert_p->cert = el.get_value<std::string>();
|
named_cert_p->cert = el.get_value<std::string>();
|
||||||
named_cert_p->uuid = uuid_util::uuid_t::generate().string();
|
named_cert_p->uuid = uuid_util::uuid_t::generate().string();
|
||||||
|
named_cert_p->perm = PERM::_all;
|
||||||
client.named_devices.emplace_back(named_cert_p);
|
client.named_devices.emplace_back(named_cert_p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,9 +311,10 @@ namespace nvhttp {
|
|||||||
if (root.count("named_devices")) {
|
if (root.count("named_devices")) {
|
||||||
for (auto &[_, el] : root.get_child("named_devices")) {
|
for (auto &[_, el] : root.get_child("named_devices")) {
|
||||||
auto named_cert_p = std::make_shared<crypto::named_cert_t>();
|
auto named_cert_p = std::make_shared<crypto::named_cert_t>();
|
||||||
named_cert_p->name = el.get_child("name").get_value<std::string>();
|
named_cert_p->name = el.get<std::string>("name");
|
||||||
named_cert_p->cert = el.get_child("cert").get_value<std::string>();
|
named_cert_p->cert = el.get<std::string>("cert");
|
||||||
named_cert_p->uuid = el.get_child("uuid").get_value<std::string>();
|
named_cert_p->uuid = el.get<std::string>("uuid");
|
||||||
|
named_cert_p->perm = (PERM)el.get("perm", (uint32_t)PERM::_all);
|
||||||
client.named_devices.emplace_back(named_cert_p);
|
client.named_devices.emplace_back(named_cert_p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -507,6 +511,12 @@ namespace nvhttp {
|
|||||||
}
|
}
|
||||||
named_cert_p->cert = std::move(client.cert);
|
named_cert_p->cert = std::move(client.cert);
|
||||||
named_cert_p->uuid = uuid_util::uuid_t::generate().string();
|
named_cert_p->uuid = uuid_util::uuid_t::generate().string();
|
||||||
|
// If the device is the first one paired with the server, assign full permission.
|
||||||
|
if (client_root.named_devices.empty()) {
|
||||||
|
named_cert_p->perm = PERM::_all;
|
||||||
|
} else {
|
||||||
|
named_cert_p->perm = PERM::_default;
|
||||||
|
}
|
||||||
|
|
||||||
auto it = map_id_sess.find(client.uniqueID);
|
auto it = map_id_sess.find(client.uniqueID);
|
||||||
map_id_sess.erase(it);
|
map_id_sess.erase(it);
|
||||||
@@ -765,8 +775,6 @@ namespace nvhttp {
|
|||||||
|
|
||||||
int pair_status = 0;
|
int pair_status = 0;
|
||||||
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
|
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
|
||||||
BOOST_LOG(info) << "Device " << get_verified_cert(request)->name << " getting server info!!!";
|
|
||||||
|
|
||||||
auto args = request->parse_query_string();
|
auto args = request->parse_query_string();
|
||||||
auto clientID = args.find("uniqueid"s);
|
auto clientID = args.find("uniqueid"s);
|
||||||
|
|
||||||
@@ -789,25 +797,36 @@ namespace nvhttp {
|
|||||||
tree.put("root.ExternalPort", net::map_port(PORT_HTTP));
|
tree.put("root.ExternalPort", net::map_port(PORT_HTTP));
|
||||||
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
|
tree.put("root.MaxLumaPixelsHEVC", video::active_hevc_mode > 1 ? "1869449984" : "0");
|
||||||
|
|
||||||
|
crypto::named_cert_t* named_cert_p;
|
||||||
|
|
||||||
// Only include the MAC address for requests sent from paired clients over HTTPS.
|
// Only include the MAC address for requests sent from paired clients over HTTPS.
|
||||||
// For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore.
|
// For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore.
|
||||||
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
|
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
|
||||||
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address())));
|
tree.put("root.mac", platf::get_mac_address(net::addr_to_normalized_string(local_endpoint.address())));
|
||||||
|
|
||||||
pt::ptree& root_node = tree.get_child("root");
|
named_cert_p = get_verified_cert(request);
|
||||||
|
if (named_cert_p->perm >= PERM::server_cmd) {
|
||||||
|
pt::ptree& root_node = tree.get_child("root");
|
||||||
|
|
||||||
if (config::sunshine.server_cmds.size() > 0) {
|
if (config::sunshine.server_cmds.size() > 0) {
|
||||||
// Broadcast server_cmds
|
// Broadcast server_cmds
|
||||||
for (const auto& cmd : config::sunshine.server_cmds) {
|
for (const auto& cmd : config::sunshine.server_cmds) {
|
||||||
pt::ptree cmd_node;
|
pt::ptree cmd_node;
|
||||||
cmd_node.put_value(cmd.cmd_name);
|
cmd_node.put_value(cmd.cmd_name);
|
||||||
root_node.push_back(std::make_pair("ServerCommand", cmd_node));
|
root_node.push_back(std::make_pair("ServerCommand", cmd_node));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
BOOST_LOG(debug) << "Permission Get ServerCommand denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
tree.put("root.VirtualDisplayCapable", true);
|
tree.put("root.VirtualDisplayCapable", true);
|
||||||
tree.put("root.VirtualDisplayDriverReady", proc::vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK);
|
if (named_cert_p->perm < PERM::list) {
|
||||||
|
tree.put("root.VirtualDisplayDriverReady", true);
|
||||||
|
} else {
|
||||||
|
tree.put("root.VirtualDisplayDriverReady", proc::vDisplayDriverStatus == VDISPLAY::DRIVER_STATUS::OK);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -860,10 +879,16 @@ namespace nvhttp {
|
|||||||
}
|
}
|
||||||
tree.put("root.ServerCodecModeSupport", codec_mode_flags);
|
tree.put("root.ServerCodecModeSupport", codec_mode_flags);
|
||||||
|
|
||||||
auto current_appid = proc::proc.running();
|
|
||||||
tree.put("root.PairStatus", pair_status);
|
tree.put("root.PairStatus", pair_status);
|
||||||
tree.put("root.currentgame", current_appid);
|
|
||||||
tree.put("root.state", current_appid > 0 ? "SUNSHINE_SERVER_BUSY" : "SUNSHINE_SERVER_FREE");
|
if constexpr (std::is_same_v<SunshineHTTPS, T>) {
|
||||||
|
auto current_appid = proc::proc.running();
|
||||||
|
tree.put("root.currentgame", current_appid);
|
||||||
|
tree.put("root.state", current_appid > 0 ? "SUNSHINE_SERVER_BUSY" : "SUNSHINE_SERVER_FREE");
|
||||||
|
} else {
|
||||||
|
tree.put("root.currentgame", 0);
|
||||||
|
tree.put("root.state", "SUNSHINE_SERVER_FREE");
|
||||||
|
}
|
||||||
|
|
||||||
std::ostringstream data;
|
std::ostringstream data;
|
||||||
|
|
||||||
@@ -880,6 +905,7 @@ namespace nvhttp {
|
|||||||
pt::ptree named_cert_node;
|
pt::ptree named_cert_node;
|
||||||
named_cert_node.put("name"s, named_cert_p->name);
|
named_cert_node.put("name"s, named_cert_p->name);
|
||||||
named_cert_node.put("uuid"s, named_cert_p->uuid);
|
named_cert_node.put("uuid"s, named_cert_p->uuid);
|
||||||
|
named_cert_node.put("perm", (uint32_t)named_cert_p->perm);
|
||||||
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
named_cert_nodes.push_back(std::make_pair(""s, named_cert_node));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -904,23 +930,37 @@ namespace nvhttp {
|
|||||||
|
|
||||||
apps.put("<xmlattr>.status_code", 200);
|
apps.put("<xmlattr>.status_code", 200);
|
||||||
|
|
||||||
for (auto &proc : proc::proc.get_apps()) {
|
auto named_cert_p = get_verified_cert(request);
|
||||||
|
if (named_cert_p->perm < PERM::list) {
|
||||||
|
BOOST_LOG(debug) << "Permission ListApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
|
|
||||||
pt::ptree app;
|
pt::ptree app;
|
||||||
|
|
||||||
app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0);
|
app.put("IsHdrSupported"s, 0);
|
||||||
app.put("AppTitle"s, proc.name);
|
app.put("AppTitle"s, "Permission Denied");
|
||||||
app.put("ID", proc.id);
|
app.put("ID", "PERMISSION_DENIED");
|
||||||
|
|
||||||
apps.push_back(std::make_pair("App", std::move(app)));
|
apps.push_back(std::make_pair("App", std::move(app)));
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
for (auto &proc : proc::proc.get_apps()) {
|
||||||
|
pt::ptree app;
|
||||||
|
|
||||||
|
app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0);
|
||||||
|
app.put("AppTitle"s, proc.name);
|
||||||
|
app.put("ID", proc.id);
|
||||||
|
|
||||||
|
apps.push_back(std::make_pair("App", std::move(app)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
launch(bool &host_audio, resp_https_t response, req_https_t request) {
|
launch(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||||
print_req<SunshineHTTPS>(request);
|
print_req<SunshineHTTPS>(request);
|
||||||
|
|
||||||
auto named_cert_p = get_verified_cert(request);
|
|
||||||
|
|
||||||
pt::ptree tree;
|
pt::ptree tree;
|
||||||
auto g = util::fail_guard([&]() {
|
auto g = util::fail_guard([&]() {
|
||||||
std::ostringstream data;
|
std::ostringstream data;
|
||||||
@@ -930,6 +970,17 @@ namespace nvhttp {
|
|||||||
response->close_connection_after_response = true;
|
response->close_connection_after_response = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auto named_cert_p = get_verified_cert(request);
|
||||||
|
if (named_cert_p->perm < PERM::launch) {
|
||||||
|
BOOST_LOG(debug) << "Permission LaunchApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
|
|
||||||
|
tree.put("root.resume", 0);
|
||||||
|
tree.put("root.<xmlattr>.status_code", 503);
|
||||||
|
tree.put("root.<xmlattr>.status_message", "Permission denied");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (rtsp_stream::session_count() == config::stream.channels) {
|
if (rtsp_stream::session_count() == config::stream.channels) {
|
||||||
tree.put("root.resume", 0);
|
tree.put("root.resume", 0);
|
||||||
tree.put("root.<xmlattr>.status_code", 503);
|
tree.put("root.<xmlattr>.status_code", 503);
|
||||||
@@ -1018,8 +1069,6 @@ namespace nvhttp {
|
|||||||
resume(bool &host_audio, resp_https_t response, req_https_t request) {
|
resume(bool &host_audio, resp_https_t response, req_https_t request) {
|
||||||
print_req<SunshineHTTPS>(request);
|
print_req<SunshineHTTPS>(request);
|
||||||
|
|
||||||
auto named_cert_p = get_verified_cert(request);
|
|
||||||
|
|
||||||
pt::ptree tree;
|
pt::ptree tree;
|
||||||
auto g = util::fail_guard([&]() {
|
auto g = util::fail_guard([&]() {
|
||||||
std::ostringstream data;
|
std::ostringstream data;
|
||||||
@@ -1029,6 +1078,17 @@ namespace nvhttp {
|
|||||||
response->close_connection_after_response = true;
|
response->close_connection_after_response = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auto named_cert_p = get_verified_cert(request);
|
||||||
|
if (named_cert_p->perm < PERM::view) {
|
||||||
|
BOOST_LOG(debug) << "Permission ViewApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
|
|
||||||
|
tree.put("root.resume", 0);
|
||||||
|
tree.put("root.<xmlattr>.status_code", 503);
|
||||||
|
tree.put("root.<xmlattr>.status_message", "Permission denied");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// It is possible that due a race condition that this if-statement gives a false negative,
|
// It is possible that due a race condition that this if-statement gives a false negative,
|
||||||
// that is automatically resolved in rtsp_server_t
|
// that is automatically resolved in rtsp_server_t
|
||||||
if (rtsp_stream::session_count() == config::stream.channels) {
|
if (rtsp_stream::session_count() == config::stream.channels) {
|
||||||
@@ -1115,6 +1175,17 @@ namespace nvhttp {
|
|||||||
response->close_connection_after_response = true;
|
response->close_connection_after_response = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auto named_cert_p = get_verified_cert(request);
|
||||||
|
if (named_cert_p->perm < PERM::launch) {
|
||||||
|
BOOST_LOG(debug) << "Permission CancelApp denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
|
|
||||||
|
tree.put("root.resume", 0);
|
||||||
|
tree.put("root.<xmlattr>.status_code", 503);
|
||||||
|
tree.put("root.<xmlattr>.status_message", "Permission denied");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// It is possible that due a race condition that this if-statement gives a false positive,
|
// It is possible that due a race condition that this if-statement gives a false positive,
|
||||||
// the client should try again
|
// the client should try again
|
||||||
if (rtsp_stream::session_count() != 0) {
|
if (rtsp_stream::session_count() != 0) {
|
||||||
@@ -1137,9 +1208,27 @@ namespace nvhttp {
|
|||||||
appasset(resp_https_t response, req_https_t request) {
|
appasset(resp_https_t response, req_https_t request) {
|
||||||
print_req<SunshineHTTPS>(request);
|
print_req<SunshineHTTPS>(request);
|
||||||
|
|
||||||
|
auto fg = util::fail_guard([&]() {
|
||||||
|
response->write(SimpleWeb::StatusCode::server_error_internal_server_error);
|
||||||
|
response->close_connection_after_response = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
auto named_cert_p = get_verified_cert(request);
|
||||||
|
|
||||||
|
if (named_cert_p->perm < PERM::list) {
|
||||||
|
BOOST_LOG(debug) << "Permission Get AppAsset denied for [" << named_cert_p->name << "] (" << (uint32_t)named_cert_p->perm << ")";
|
||||||
|
|
||||||
|
fg.disable();
|
||||||
|
response->write(SimpleWeb::StatusCode::client_error_unauthorized);
|
||||||
|
response->close_connection_after_response = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto args = request->parse_query_string();
|
auto args = request->parse_query_string();
|
||||||
auto app_image = proc::proc.get_app_image(util::from_view(get_arg(args, "appid")));
|
auto app_image = proc::proc.get_app_image(util::from_view(get_arg(args, "appid")));
|
||||||
|
|
||||||
|
fg.disable();
|
||||||
|
|
||||||
std::ifstream in(app_image, std::ios::binary);
|
std::ifstream in(app_image, std::ios::binary);
|
||||||
SimpleWeb::CaseInsensitiveMultimap headers;
|
SimpleWeb::CaseInsensitiveMultimap headers;
|
||||||
headers.emplace("Content-Type", "image/png");
|
headers.emplace("Content-Type", "image/png");
|
||||||
@@ -1180,16 +1269,21 @@ namespace nvhttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool verified = false;
|
bool verified = false;
|
||||||
|
p_named_cert_t named_cert_p;
|
||||||
|
|
||||||
auto fg = util::fail_guard([&]() {
|
auto fg = util::fail_guard([&]() {
|
||||||
char subject_name[256];
|
char subject_name[256];
|
||||||
|
|
||||||
X509_NAME_oneline(X509_get_subject_name(x509.get()), subject_name, sizeof(subject_name));
|
X509_NAME_oneline(X509_get_subject_name(x509.get()), subject_name, sizeof(subject_name));
|
||||||
|
|
||||||
BOOST_LOG(debug) << subject_name << " -- "sv << (verified ? "verified"sv : "denied"sv);
|
if (verified) {
|
||||||
|
BOOST_LOG(debug) << subject_name << " -- "sv << "verified, device name: "sv << named_cert_p->name;
|
||||||
|
} else {
|
||||||
|
BOOST_LOG(debug) << subject_name << " -- "sv << "denied"sv;
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
p_named_cert_t named_cert_p;
|
|
||||||
auto err_str = cert_chain.verify(x509.get(), named_cert_p);
|
auto err_str = cert_chain.verify(x509.get(), named_cert_p);
|
||||||
if (err_str) {
|
if (err_str) {
|
||||||
BOOST_LOG(warning) << "SSL Verification error :: "sv << err_str;
|
BOOST_LOG(warning) << "SSL Verification error :: "sv << err_str;
|
||||||
@@ -1199,8 +1293,6 @@ namespace nvhttp {
|
|||||||
verified = true;
|
verified = true;
|
||||||
req->userp = named_cert_p;
|
req->userp = named_cert_p;
|
||||||
|
|
||||||
BOOST_LOG(info) << "Device " << named_cert_p->name << " verified!";
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user