From aa4d38e99201e6173be9f7ed1eec77129d23e18b Mon Sep 17 00:00:00 2001 From: Yukino Song Date: Sun, 15 Sep 2024 18:42:54 +0800 Subject: [PATCH] Implement permission management for nvhttp --- src/crypto.h | 15 +++++ src/nvhttp.cpp | 148 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 135 insertions(+), 28 deletions(-) diff --git a/src/crypto.h b/src/crypto.h index 45c876f6..98e6d3a2 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -34,10 +34,25 @@ namespace crypto { using pkey_ctx_t = util::safe_ptr; using bignum_t = util::safe_ptr; + /** + * @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 { std::string name; std::string uuid; std::string cert; + PERM perm; }; using p_named_cert_t = std::shared_ptr; diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 7981d8d9..c9d369c0 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -47,6 +47,7 @@ namespace nvhttp { namespace pt = boost::property_tree; using p_named_cert_t = crypto::p_named_cert_t; + using PERM = crypto::PERM; struct client_t { std::vector named_devices; @@ -242,6 +243,7 @@ namespace nvhttp { named_cert_node.put("name"s, final_name); named_cert_node.put("cert"s, named_cert_p->cert); 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)); } } @@ -299,6 +301,7 @@ namespace nvhttp { named_cert_p->name = ""s; named_cert_p->cert = el.get_value(); named_cert_p->uuid = uuid_util::uuid_t::generate().string(); + named_cert_p->perm = PERM::_all; client.named_devices.emplace_back(named_cert_p); } } @@ -308,9 +311,10 @@ namespace nvhttp { if (root.count("named_devices")) { for (auto &[_, el] : root.get_child("named_devices")) { auto named_cert_p = std::make_shared(); - named_cert_p->name = el.get_child("name").get_value(); - named_cert_p->cert = el.get_child("cert").get_value(); - named_cert_p->uuid = el.get_child("uuid").get_value(); + named_cert_p->name = el.get("name"); + named_cert_p->cert = el.get("cert"); + named_cert_p->uuid = el.get("uuid"); + named_cert_p->perm = (PERM)el.get("perm", (uint32_t)PERM::_all); 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->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); map_id_sess.erase(it); @@ -765,8 +775,6 @@ namespace nvhttp { int pair_status = 0; if constexpr (std::is_same_v) { - BOOST_LOG(info) << "Device " << get_verified_cert(request)->name << " getting server info!!!"; - auto args = request->parse_query_string(); auto clientID = args.find("uniqueid"s); @@ -789,25 +797,36 @@ namespace nvhttp { tree.put("root.ExternalPort", net::map_port(PORT_HTTP)); 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. // For HTTP requests, use a placeholder MAC address that Moonlight knows to ignore. if constexpr (std::is_same_v) { 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) { - // Broadcast server_cmds - for (const auto& cmd : config::sunshine.server_cmds) { - pt::ptree cmd_node; - cmd_node.put_value(cmd.cmd_name); - root_node.push_back(std::make_pair("ServerCommand", cmd_node)); + if (config::sunshine.server_cmds.size() > 0) { + // Broadcast server_cmds + for (const auto& cmd : config::sunshine.server_cmds) { + pt::ptree cmd_node; + cmd_node.put_value(cmd.cmd_name); + 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 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 } else { @@ -860,10 +879,16 @@ namespace nvhttp { } tree.put("root.ServerCodecModeSupport", codec_mode_flags); - auto current_appid = proc::proc.running(); 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) { + 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; @@ -880,6 +905,7 @@ namespace nvhttp { pt::ptree named_cert_node; named_cert_node.put("name"s, named_cert_p->name); 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)); } @@ -904,23 +930,37 @@ namespace nvhttp { apps.put(".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; - app.put("IsHdrSupported"s, video::active_hevc_mode == 3 ? 1 : 0); - app.put("AppTitle"s, proc.name); - app.put("ID", proc.id); + app.put("IsHdrSupported"s, 0); + app.put("AppTitle"s, "Permission Denied"); + app.put("ID", "PERMISSION_DENIED"); 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 launch(bool &host_audio, resp_https_t response, req_https_t request) { print_req(request); - auto named_cert_p = get_verified_cert(request); - pt::ptree tree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -930,6 +970,17 @@ namespace nvhttp { 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..status_code", 503); + tree.put("root..status_message", "Permission denied"); + + return; + } + if (rtsp_stream::session_count() == config::stream.channels) { tree.put("root.resume", 0); tree.put("root..status_code", 503); @@ -1018,8 +1069,6 @@ namespace nvhttp { resume(bool &host_audio, resp_https_t response, req_https_t request) { print_req(request); - auto named_cert_p = get_verified_cert(request); - pt::ptree tree; auto g = util::fail_guard([&]() { std::ostringstream data; @@ -1029,6 +1078,17 @@ namespace nvhttp { 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..status_code", 503); + tree.put("root..status_message", "Permission denied"); + + return; + } + // It is possible that due a race condition that this if-statement gives a false negative, // that is automatically resolved in rtsp_server_t if (rtsp_stream::session_count() == config::stream.channels) { @@ -1115,6 +1175,17 @@ namespace nvhttp { 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..status_code", 503); + tree.put("root..status_message", "Permission denied"); + + return; + } + // It is possible that due a race condition that this if-statement gives a false positive, // the client should try again if (rtsp_stream::session_count() != 0) { @@ -1137,9 +1208,27 @@ namespace nvhttp { appasset(resp_https_t response, req_https_t request) { print_req(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 app_image = proc::proc.get_app_image(util::from_view(get_arg(args, "appid"))); + fg.disable(); + std::ifstream in(app_image, std::ios::binary); SimpleWeb::CaseInsensitiveMultimap headers; headers.emplace("Content-Type", "image/png"); @@ -1180,16 +1269,21 @@ namespace nvhttp { } bool verified = false; + p_named_cert_t named_cert_p; auto fg = util::fail_guard([&]() { char subject_name[256]; 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); if (err_str) { BOOST_LOG(warning) << "SSL Verification error :: "sv << err_str; @@ -1199,8 +1293,6 @@ namespace nvhttp { verified = true; req->userp = named_cert_p; - BOOST_LOG(info) << "Device " << named_cert_p->name << " verified!"; - return true; };