diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 88eeae19..01e1531f 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -740,6 +740,43 @@ namespace confighttp { } } + void + getOTP(resp_https_t response, req_https_t request) { + if (!authenticate(response, request)) return; + + print_req(request); + + pt::ptree outputTree; + + auto g = util::fail_guard([&]() { + std::ostringstream data; + pt::write_json(data, outputTree); + response->write(data.str()); + }); + + try { + auto args = request->parse_query_string(); + auto it = args.find("passphrase"); + if (it == std::end(args)) { + throw std::runtime_error("Passphrase not provided!"); + } + + if (it->second.size() < 4) { + throw std::runtime_error("Passphrase too short!"); + } + + outputTree.put("otp", nvhttp::request_otp(it->second)); + outputTree.put("statue", true); + outputTree.put("message", "OTP created, effective within 1 minute.") + } + catch (std::exception &e) { + BOOST_LOG(warning) << "OTP creation failed: "sv << e.what(); + outputTree.put("status", false); + outputTree.put("message", e.what()); + return; + } + } + void unpairAll(resp_https_t response, req_https_t request) { if (!authenticate(response, request)) return; @@ -847,6 +884,7 @@ namespace confighttp { server.resource["^/welcome/?$"]["GET"] = getWelcomePage; server.resource["^/troubleshooting/?$"]["GET"] = getTroubleshootingPage; server.resource["^/api/pin$"]["POST"] = savePin; + server.resource["^/api/otp$"]["GET"] = getOTP; server.resource["^/api/apps$"]["GET"] = getApps; server.resource["^/api/logs$"]["GET"] = getLogs; server.resource["^/api/apps$"]["POST"] = saveApp; diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index 89bf27fc..d826d77e 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -8,6 +8,7 @@ // standard includes #include #include +#include // lib includes #include @@ -17,7 +18,6 @@ #include #include #include -#include // local includes #include "config.h" @@ -47,6 +47,9 @@ namespace nvhttp { namespace pt = boost::property_tree; crypto::cert_chain_t cert_chain; + static std::string one_time_pin; + static std::string otp_passphrase; + static std::chrono::time_point otp_creation_time; class SunshineHttpsServer: public SimpleWeb::Server { public: @@ -567,18 +570,19 @@ namespace nvhttp { } auto uniqID { get_arg(args, "uniqueid") }; - auto deviceName { get_arg(args, "devicename") }; auto sess_it = map_id_sess.find(uniqID); - if (deviceName == "roth"sv) { - deviceName = "Legacy Moonlight Client"; - } - args_t::const_iterator it; if (it = args.find("phrase"); it != std::end(args)) { if (it->second == "getservercert"sv) { pair_session_t sess; + auto deviceName { get_arg(args, "devicename") }; + + if (deviceName == "roth"sv) { + deviceName = "Legacy Moonlight Client"; + } + sess.client.uniqueID = std::move(uniqID); sess.client.deviceName = std::move(deviceName); sess.client.cert = util::from_hex_vec(get_arg(args, "clientcert"), true); @@ -587,6 +591,30 @@ namespace nvhttp { auto ptr = map_id_sess.emplace(sess.client.uniqueID, std::move(sess)).first; ptr->second.async_insert_pin.salt = std::move(get_arg(args, "salt")); + + auto it = args.find("otpauth"); + if (it != std::end(args)) { + if (one_time_pin.empty() || (std::chrono::steady_clock::now() - otp_creation_time > OTP_EXPIRE_DURATION)) { + one_time_pin.clear(); + otp_passphrase.clear(); + tree.put("root..status_code", 503); + tree.put("root..status_message", "OTP auth not available."); + } else { + auto hash = util::hex(crypto::hash(one_time_pin + ptr->second.async_insert_pin.salt + otp_passphrase)); + + if (hash.to_string_view() == it->second) { + getservercert(ptr->second, tree, one_time_pin); + one_time_pin.clear(); + otp_passphrase.clear(); + return; + } + } + + // Always return positive, attackers will fail in the next steps. + getservercert(ptr->second, tree, crypto::rand(16)); + return; + } + if (config::sunshine.flags[config::flag::PIN_STDIN]) { std::string pin; @@ -1183,6 +1211,18 @@ namespace nvhttp { tcp.join(); } + std::string request_otp(const std::string& passphrase) { + if (passphrase.size() < 4) { + return ""; + } + + otp_passphrase = passphrase; + one_time_pin = crypto::rand_alphabet(4, "0123456789"sv); + otp_creation_time = std::chrono::steady_clock::now(); + + return one_time_pin; + } + void erase_all_clients() { client_t client; diff --git a/src/nvhttp.h b/src/nvhttp.h index 1f8726c3..7bd70d32 100644 --- a/src/nvhttp.h +++ b/src/nvhttp.h @@ -7,6 +7,7 @@ // standard includes #include +#include // lib includes #include @@ -14,6 +15,8 @@ // local includes #include "thread_safe.h" +using namespace std::chrono_literals; + /** * @brief Contains all the functions and variables related to the nvhttp (GameStream) server. */ @@ -41,6 +44,8 @@ namespace nvhttp { */ constexpr auto PORT_HTTPS = -5; + constexpr auto OTP_EXPIRE_DURATION = 60s; + /** * @brief Start the nvhttp server. * @examples @@ -62,6 +67,8 @@ namespace nvhttp { bool pin(std::string pin, std::string name); + std::string request_otp(const std::string& passphrase); + /** * @brief Remove single client. * @examples