287 lines
8.1 KiB
C++
287 lines
8.1 KiB
C++
/**
|
|
* @file src/nvhttp.h
|
|
* @brief Declarations for the nvhttp (GameStream) server.
|
|
*/
|
|
// macros
|
|
#pragma once
|
|
|
|
// standard includes
|
|
#include <string>
|
|
#include <chrono>
|
|
#include <list>
|
|
|
|
// lib includes
|
|
#include <Simple-Web-Server/server_https.hpp>
|
|
#include <boost/property_tree/ptree.hpp>
|
|
|
|
// local includes
|
|
#include "crypto.h"
|
|
#include "rtsp.h"
|
|
#include "thread_safe.h"
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
/**
|
|
* @brief Contains all the functions and variables related to the nvhttp (GameStream) server.
|
|
*/
|
|
namespace nvhttp {
|
|
|
|
using args_t = SimpleWeb::CaseInsensitiveMultimap;
|
|
using cmd_list_t = std::list<crypto::command_entry_t>;
|
|
|
|
/**
|
|
* @brief The protocol version.
|
|
* @details The version of the GameStream protocol we are mocking.
|
|
* @note The negative 4th number indicates to Moonlight that this is Sunshine.
|
|
*/
|
|
constexpr auto VERSION = "7.1.431.-1";
|
|
|
|
/**
|
|
* @brief The GFE version we are replicating.
|
|
*/
|
|
constexpr auto GFE_VERSION = "3.23.0.74";
|
|
|
|
/**
|
|
* @brief The HTTP port, as a difference from the config port.
|
|
*/
|
|
constexpr auto PORT_HTTP = 0;
|
|
|
|
/**
|
|
* @brief The HTTPS port, as a difference from the config port.
|
|
*/
|
|
constexpr auto PORT_HTTPS = -5;
|
|
|
|
constexpr auto OTP_EXPIRE_DURATION = 180s;
|
|
|
|
/**
|
|
* @brief Start the nvhttp server.
|
|
* @examples
|
|
* nvhttp::start();
|
|
* @examples_end
|
|
*/
|
|
void
|
|
start();
|
|
|
|
std::string
|
|
get_arg(const args_t &args, const char *name, const char *default_value = nullptr);
|
|
|
|
// Helper function to extract command entries
|
|
cmd_list_t
|
|
extract_command_entries(const boost::property_tree::ptree& pt, const std::string& key);
|
|
|
|
std::shared_ptr<rtsp_stream::launch_session_t>
|
|
make_launch_session(bool host_audio, int appid, const args_t &args, const crypto::named_cert_t* named_cert_p);
|
|
|
|
/**
|
|
* @brief Setup the nvhttp server.
|
|
* @param pkey
|
|
* @param cert
|
|
*/
|
|
void
|
|
setup(const std::string &pkey, const std::string &cert);
|
|
|
|
class SunshineHTTPS: public SimpleWeb::HTTPS {
|
|
public:
|
|
SunshineHTTPS(boost::asio::io_context &io_context, boost::asio::ssl::context &ctx):
|
|
SimpleWeb::HTTPS(io_context, ctx) {}
|
|
|
|
virtual ~SunshineHTTPS() {
|
|
// Gracefully shutdown the TLS connection
|
|
SimpleWeb::error_code ec;
|
|
shutdown(ec);
|
|
}
|
|
};
|
|
|
|
enum class PAIR_PHASE {
|
|
NONE, ///< Sunshine is not in a pairing phase
|
|
GETSERVERCERT, ///< Sunshine is in the get server certificate phase
|
|
CLIENTCHALLENGE, ///< Sunshine is in the client challenge phase
|
|
SERVERCHALLENGERESP, ///< Sunshine is in the server challenge response phase
|
|
CLIENTPAIRINGSECRET ///< Sunshine is in the client pairing secret phase
|
|
};
|
|
|
|
struct pair_session_t {
|
|
struct {
|
|
std::string uniqueID = {};
|
|
std::string cert = {};
|
|
std::string name = {};
|
|
} client;
|
|
|
|
std::unique_ptr<crypto::aes_t> cipher_key = {};
|
|
std::vector<uint8_t> clienthash = {};
|
|
|
|
std::string serversecret = {};
|
|
std::string serverchallenge = {};
|
|
|
|
struct {
|
|
util::Either<
|
|
std::shared_ptr<typename SimpleWeb::ServerBase<SimpleWeb::HTTP>::Response>,
|
|
std::shared_ptr<typename SimpleWeb::ServerBase<SunshineHTTPS>::Response>>
|
|
response;
|
|
std::string salt = {};
|
|
} async_insert_pin;
|
|
|
|
/**
|
|
* @brief used as a security measure to prevent out of order calls
|
|
*/
|
|
PAIR_PHASE last_phase = PAIR_PHASE::NONE;
|
|
};
|
|
|
|
/**
|
|
* @brief removes the temporary pairing session
|
|
* @param sess
|
|
*/
|
|
void
|
|
remove_session(const pair_session_t &sess);
|
|
|
|
/**
|
|
* @brief Pair, phase 1
|
|
*
|
|
* Moonlight will send a salt and client certificate, we'll also need the user provided pin.
|
|
*
|
|
* PIN and SALT will be used to derive a shared AES key that needs to be stored
|
|
* in order to be used to decrypt_symmetric in the next phases.
|
|
*
|
|
* At this stage we only have to send back our public certificate.
|
|
*/
|
|
void
|
|
getservercert(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &pin);
|
|
|
|
/**
|
|
* @brief Pair, phase 2
|
|
*
|
|
* Using the AES key that we generated in phase 1 we have to decrypt the client challenge,
|
|
*
|
|
* We generate a SHA256 hash with the following:
|
|
* - Decrypted challenge
|
|
* - Server certificate signature
|
|
* - Server secret: a randomly generated secret
|
|
*
|
|
* The hash + server_challenge will then be AES encrypted and sent as the `challengeresponse` in the returned XML
|
|
*/
|
|
void
|
|
clientchallenge(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &challenge);
|
|
|
|
/**
|
|
* @brief Pair, phase 3
|
|
*
|
|
* Moonlight will send back a `serverchallengeresp`: an AES encrypted client hash,
|
|
* we have to send back the `pairingsecret`:
|
|
* using our private key we have to sign the certificate_signature + server_secret (generated in phase 2)
|
|
*/
|
|
void
|
|
serverchallengeresp(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &encrypted_response);
|
|
|
|
/**
|
|
* @brief Pair, phase 4 (final)
|
|
*
|
|
* We now have to use everything we exchanged before in order to verify and finally pair the clients
|
|
*
|
|
* We'll check the client_hash obtained at phase 3, it should contain the following:
|
|
* - The original server_challenge
|
|
* - The signature of the X509 client_cert
|
|
* - The unencrypted client_pairing_secret
|
|
* We'll check that SHA256(server_challenge + client_public_cert_signature + client_secret) == client_hash
|
|
*
|
|
* Then using the client certificate public key we should be able to verify that
|
|
* the client secret has been signed by Moonlight
|
|
*/
|
|
void
|
|
clientpairingsecret(pair_session_t &sess, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
|
|
|
|
/**
|
|
* @brief Compare the user supplied pin to the Moonlight pin.
|
|
* @param pin The user supplied pin.
|
|
* @param name The user supplied name.
|
|
* @return `true` if the pin is correct, `false` otherwise.
|
|
* @examples
|
|
* bool pin_status = nvhttp::pin("1234", "laptop");
|
|
* @examples_end
|
|
*/
|
|
bool
|
|
pin(std::string pin, std::string name);
|
|
|
|
std::string request_otp(const std::string& passphrase, const std::string& deviceName);
|
|
|
|
/**
|
|
* @brief Remove single client.
|
|
* @examples
|
|
* nvhttp::unpair_client("4D7BB2DD-5704-A405-B41C-891A022932E1");
|
|
* @examples_end
|
|
*/
|
|
int
|
|
unpair_client(std::string uniqueid);
|
|
|
|
/**
|
|
* @brief Get all paired clients.
|
|
* @return The list of all paired clients.
|
|
* @examples
|
|
* boost::property_tree::ptree clients = nvhttp::get_all_clients();
|
|
* @examples_end
|
|
*/
|
|
boost::property_tree::ptree
|
|
get_all_clients();
|
|
|
|
/**
|
|
* @brief Remove all paired clients.
|
|
* @examples
|
|
* nvhttp::erase_all_clients();
|
|
* @examples_end
|
|
*/
|
|
void
|
|
erase_all_clients();
|
|
|
|
/**
|
|
* @brief Stops a session.
|
|
*
|
|
* @param session The session
|
|
* @param[in] graceful Whether to stop gracefully
|
|
*/
|
|
void stop_session(stream::session_t& session, bool graceful);
|
|
|
|
/**
|
|
* @brief Finds and stop session.
|
|
*
|
|
* @param[in] uuid The uuid string
|
|
* @param[in] graceful Whether to stop gracefully
|
|
*/
|
|
bool find_and_stop_session(const std::string& uuid, bool graceful);
|
|
|
|
/**
|
|
* @brief Update device info associated to the session
|
|
*
|
|
* @param session The session
|
|
* @param[in] name New name
|
|
* @param[in] newPerm New permission
|
|
*/
|
|
void update_session_info(stream::session_t& session, const std::string& name, const crypto::PERM newPerm);
|
|
|
|
/**
|
|
* @brief Finds and udpate session information.
|
|
*
|
|
* @param[in] uuid The uuid string
|
|
* @param[in] name New name
|
|
* @param[in] newPerm New permission
|
|
*/
|
|
bool find_and_udpate_session_info(const std::string& uuid, const std::string& name, const crypto::PERM newPerm);
|
|
|
|
/**
|
|
* @brief Update device info
|
|
*
|
|
* @param[in] uuid The uuid string
|
|
* @param[in] name New name
|
|
* @param[in] do_cmds The do commands
|
|
* @param[in] undo_cmds The undo commands
|
|
* @param[in] newPerm New permission
|
|
*
|
|
* @return Whether the update is successful
|
|
*/
|
|
bool update_device_info(
|
|
const std::string& uuid,
|
|
const std::string& name,
|
|
const cmd_list_t& do_cmds,
|
|
const cmd_list_t& undo_cmds,
|
|
const crypto::PERM newPerm
|
|
);
|
|
} // namespace nvhttp
|