Merge remote-tracking branch 'origin/master'

This commit is contained in:
Yukino Song
2025-01-18 14:57:43 +08:00
16 changed files with 584 additions and 111 deletions

View File

@@ -70,23 +70,27 @@ set(OPENSSL_LIBRARIES
libcrypto.a) libcrypto.a)
list(PREPEND PLATFORM_LIBRARIES list(PREPEND PLATFORM_LIBRARIES
${CURL_STATIC_LIBRARIES}
avrt
d3d11
D3DCompiler
dwmapi
dxgi
iphlpapi
ksuser
libssp.a
libstdc++.a libstdc++.a
libwinpthread.a libwinpthread.a
libssp.a minhook::minhook
nlohmann_json::nlohmann_json
ntdll ntdll
ksuser
wsock32
ws2_32
d3d11 dxgi D3DCompiler
setupapi setupapi
dwmapi
userenv
synchronization.lib
avrt
iphlpapi
shlwapi shlwapi
PkgConfig::NLOHMANN_JSON synchronization.lib
${CURL_STATIC_LIBRARIES}) userenv
ws2_32
wsock32
)
if(SUNSHINE_ENABLE_TRAY) if(SUNSHINE_ENABLE_TRAY)
list(APPEND PLATFORM_TARGET_FILES list(APPEND PLATFORM_TARGET_FILES

View File

@@ -28,7 +28,7 @@ include_directories(SYSTEM ${MINIUPNP_INCLUDE_DIRS})
# ffmpeg pre-compiled binaries # ffmpeg pre-compiled binaries
if(NOT DEFINED FFMPEG_PREPARED_BINARIES) if(NOT DEFINED FFMPEG_PREPARED_BINARIES)
if(WIN32) if(WIN32)
set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid vpl MinHook) set(FFMPEG_PLATFORM_LIBRARIES mfplat ole32 strmiids mfuuid vpl)
elseif(UNIX AND NOT APPLE) elseif(UNIX AND NOT APPLE)
set(FFMPEG_PLATFORM_LIBRARIES numa va va-drm va-x11 X11) set(FFMPEG_PLATFORM_LIBRARIES numa va va-drm va-x11 X11)
endif() endif()

View File

@@ -1,4 +1,12 @@
# windows specific dependencies # windows specific dependencies
# nlohmann_json # nlohmann_json
pkg_check_modules(NLOHMANN_JSON nlohmann_json REQUIRED IMPORTED_TARGET) find_package(nlohmann_json CONFIG 3.11 REQUIRED)
# Make sure MinHook is installed
find_library(MINHOOK_LIBRARY libMinHook.a REQUIRED)
find_path(MINHOOK_INCLUDE_DIR MinHook.h PATH_SUFFIXES include REQUIRED)
add_library(minhook::minhook STATIC IMPORTED)
set_property(TARGET minhook::minhook PROPERTY IMPORTED_LOCATION ${MINHOOK_LIBRARY})
target_include_directories(minhook::minhook INTERFACE ${MINHOOK_INCLUDE_DIR})

View File

@@ -674,11 +674,44 @@ namespace display_device {
scheduler_option.m_execution = SchedulerOptions::Execution::ScheduledOnly; scheduler_option.m_execution = SchedulerOptions::Execution::ScheduledOnly;
} }
DD_DATA.sm_instance->schedule([try_once = (option == revert_option_e::try_once)](auto &settings_iface, auto &stop_token) { DD_DATA.sm_instance->schedule([try_once = (option == revert_option_e::try_once), tried_out_devices = std::set<std::string> {}](auto &settings_iface, auto &stop_token) mutable {
// Here we want to keep retrying indefinitely until we succeed. if (try_once) {
if (settings_iface.revertSettings() || try_once) { std::ignore = settings_iface.revertSettings();
stop_token.requestStop(); stop_token.requestStop();
return;
} }
auto available_devices { [&settings_iface]() {
const auto devices { settings_iface.enumAvailableDevices() };
std::set<std::string> parsed_devices;
std::transform(
std::begin(devices), std::end(devices),
std::inserter(parsed_devices, std::end(parsed_devices)),
[](const auto &device) { return device.m_device_id + " - " + device.m_friendly_name; });
return parsed_devices;
}() };
if (available_devices == tried_out_devices) {
BOOST_LOG(debug) << "Skipping reverting configuration, because no newly added/removed devices were detected since last check. Currently available devices:\n"
<< toJson(available_devices);
return;
}
using enum SettingsManagerInterface::RevertResult;
if (const auto result { settings_iface.revertSettings() }; result == Ok) {
stop_token.requestStop();
return;
}
else if (result == ApiTemporarilyUnavailable) {
// Do nothing and retry next time
return;
}
// If we have failed to revert settings then we will try to do it next time only if a device was added/removed
BOOST_LOG(warning) << "Failed to revert display device configuration (will retry once devices are added or removed). Enabling all of the available devices:\n"
<< toJson(available_devices);
tried_out_devices.swap(available_devices);
}, },
scheduler_option); scheduler_option);
} }

View File

@@ -12,7 +12,6 @@
// lib includes // lib includes
#include <Simple-Web-Server/server_http.hpp> #include <Simple-Web-Server/server_http.hpp>
#include <Simple-Web-Server/server_https.hpp>
#include <boost/asio/ssl/context.hpp> #include <boost/asio/ssl/context.hpp>
#include <boost/asio/ssl/context_base.hpp> #include <boost/asio/ssl/context_base.hpp>
#include <boost/property_tree/json_parser.hpp> #include <boost/property_tree/json_parser.hpp>
@@ -21,7 +20,6 @@
// local includes // local includes
#include "config.h" #include "config.h"
#include "crypto.h"
#include "display_device.h" #include "display_device.h"
#include "file_handler.h" #include "file_handler.h"
#include "globals.h" #include "globals.h"
@@ -63,18 +61,6 @@ namespace nvhttp {
static std::string otp_device_name; static std::string otp_device_name;
static std::chrono::time_point<std::chrono::steady_clock> otp_creation_time; static std::chrono::time_point<std::chrono::steady_clock> otp_creation_time;
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);
}
};
class SunshineHTTPSServer: public SimpleWeb::ServerBase<SunshineHTTPS> { class SunshineHTTPSServer: public SimpleWeb::ServerBase<SunshineHTTPS> {
public: public:
SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file): SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file):
@@ -154,28 +140,6 @@ namespace nvhttp {
std::string pkey; std::string pkey;
} conf_intern; } conf_intern;
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;
};
// uniqueID, session // uniqueID, session
std::unordered_map<std::string, pair_session_t> map_id_sess; std::unordered_map<std::string, pair_session_t> map_id_sess;
client_t client_root; client_t client_root;
@@ -334,7 +298,6 @@ namespace nvhttp {
client_t &client = client_root; client_t &client = client_root;
client.named_devices.push_back(named_cert_p); client.named_devices.push_back(named_cert_p);
#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 #if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1
system_tray::update_tray_paired(named_cert_p->name); system_tray::update_tray_paired(named_cert_p->name);
#endif #endif
@@ -406,12 +369,29 @@ namespace nvhttp {
return launch_session; return launch_session;
} }
void
remove_session(const pair_session_t &sess) {
map_id_sess.erase(sess.client.uniqueID);
}
void
fail_pair(pair_session_t &sess, pt::ptree &tree, const std::string status_msg) {
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", status_msg);
remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair
}
void void
getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) { getservercert(pair_session_t &sess, pt::ptree &tree, const std::string &pin) {
if (sess.last_phase != PAIR_PHASE::NONE) {
fail_pair(sess, tree, "Out of order call to getservercert");
return;
}
sess.last_phase = PAIR_PHASE::GETSERVERCERT;
if (sess.async_insert_pin.salt.size() < 32) { if (sess.async_insert_pin.salt.size() < 32) {
tree.put("root.paired", 0); fail_pair(sess, tree, "Salt too short");
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Salt too short");
return; return;
} }
@@ -428,30 +408,17 @@ namespace nvhttp {
} }
void void
serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &args) { clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) {
auto encrypted_response = util::from_hex_vec(get_arg(args, "serverchallengeresp"), true); if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) {
fail_pair(sess, tree, "Out of order call to clientchallenge");
std::vector<uint8_t> decrypted; return;
crypto::cipher::ecb_t cipher(*sess.cipher_key, false); }
sess.last_phase = PAIR_PHASE::CLIENTCHALLENGE;
cipher.decrypt(encrypted_response, decrypted);
sess.clienthash = std::move(decrypted);
auto serversecret = sess.serversecret;
auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret);
serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign));
tree.put("root.pairingsecret", util::hex_vec(serversecret, true));
tree.put("root.paired", 1);
tree.put("root.<xmlattr>.status_code", 200);
}
void
clientchallenge(pair_session_t &sess, pt::ptree &tree, const args_t &args) {
auto challenge = util::from_hex_vec(get_arg(args, "clientchallenge"), true);
if (!sess.cipher_key) {
fail_pair(sess, tree, "Cipher key not set");
return;
}
crypto::cipher::ecb_t cipher(*sess.cipher_key, false); crypto::cipher::ecb_t cipher(*sess.cipher_key, false);
std::vector<uint8_t> decrypted; std::vector<uint8_t> decrypted;
@@ -485,21 +452,58 @@ namespace nvhttp {
} }
void void
clientpairingsecret(pair_session_t &sess, pt::ptree &tree, const args_t &args) { serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) {
auto &client = sess.client; if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) {
fail_pair(sess, tree, "Out of order call to serverchallengeresp");
return;
}
sess.last_phase = PAIR_PHASE::SERVERCHALLENGERESP;
auto pairingsecret = util::from_hex_vec(get_arg(args, "clientpairingsecret"), true); if (!sess.cipher_key || sess.serversecret.empty()) {
if (pairingsecret.size() <= 16) { fail_pair(sess, tree, "Cipher key or serversecret not set");
tree.put("root.paired", 0);
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Clientpairingsecret too short");
return; return;
} }
std::string_view secret { pairingsecret.data(), 16 }; std::vector<uint8_t> decrypted;
std::string_view sign { pairingsecret.data() + secret.size(), pairingsecret.size() - secret.size() }; crypto::cipher::ecb_t cipher(*sess.cipher_key, false);
cipher.decrypt(encrypted_response, decrypted);
sess.clienthash = std::move(decrypted);
auto serversecret = sess.serversecret;
auto sign = crypto::sign256(crypto::pkey(conf_intern.pkey), serversecret);
serversecret.insert(std::end(serversecret), std::begin(sign), std::end(sign));
tree.put("root.pairingsecret", util::hex_vec(serversecret, true));
tree.put("root.paired", 1);
tree.put("root.<xmlattr>.status_code", 200);
}
void
clientpairingsecret(pair_session_t &sess, pt::ptree &tree, const std::string &client_pairing_secret) {
if (sess.last_phase != PAIR_PHASE::SERVERCHALLENGERESP) {
fail_pair(sess, tree, "Out of order call to clientpairingsecret");
return;
}
sess.last_phase = PAIR_PHASE::CLIENTPAIRINGSECRET;
auto &client = sess.client;
if (client_pairing_secret.size() <= 16) {
fail_pair(sess, tree, "Client pairing secret too short");
return;
}
std::string_view secret { client_pairing_secret.data(), 16 };
std::string_view sign { client_pairing_secret.data() + secret.size(), client_pairing_secret.size() - secret.size() };
auto x509 = crypto::x509(client.cert); auto x509 = crypto::x509(client.cert);
if (!x509) {
fail_pair(sess, tree, "Invalid client certificate");
return;
}
auto x509_sign = crypto::signature(x509); auto x509_sign = crypto::signature(x509);
std::string data; std::string data;
@@ -512,7 +516,9 @@ namespace nvhttp {
auto hash = crypto::hash(data); auto hash = crypto::hash(data);
// if hash not correct, probably MITM // if hash not correct, probably MITM
if (!std::memcmp(hash.data(), sess.clienthash.data(), hash.size()) && crypto::verify256(crypto::x509(client.cert), secret, sign)) { bool same_hash = hash.size() == sess.clienthash.size() && std::equal(hash.begin(), hash.end(), sess.clienthash.begin());
auto verify = crypto::verify256(crypto::x509(client.cert), secret, sign);
if (same_hash && verify) {
tree.put("root.paired", 1); tree.put("root.paired", 1);
auto named_cert_p = std::make_shared<crypto::named_cert_t>(); auto named_cert_p = std::make_shared<crypto::named_cert_t>();
@@ -536,10 +542,10 @@ namespace nvhttp {
add_authorized_client(named_cert_p); add_authorized_client(named_cert_p);
} }
else { else {
map_id_sess.erase(client.uniqueID);
tree.put("root.paired", 0); tree.put("root.paired", 0);
} }
remove_session(sess);
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
} }
@@ -628,7 +634,6 @@ namespace nvhttp {
} }
auto uniqID { get_arg(args, "uniqueid") }; auto uniqID { get_arg(args, "uniqueid") };
auto sess_it = map_id_sess.find(uniqID);
args_t::const_iterator it; args_t::const_iterator it;
if (it = args.find("phrase"); it != std::end(args)) { if (it = args.find("phrase"); it != std::end(args)) {
@@ -702,16 +707,29 @@ namespace nvhttp {
else if (it->second == "pairchallenge"sv) { else if (it->second == "pairchallenge"sv) {
tree.put("root.paired", 1); tree.put("root.paired", 1);
tree.put("root.<xmlattr>.status_code", 200); tree.put("root.<xmlattr>.status_code", 200);
return;
} }
} }
else if (it = args.find("clientchallenge"); it != std::end(args)) {
clientchallenge(sess_it->second, tree, args); auto sess_it = map_id_sess.find(uniqID);
if (sess_it == std::end(map_id_sess)) {
tree.put("root.<xmlattr>.status_code", 400);
tree.put("root.<xmlattr>.status_message", "Invalid uniqueid");
return;
}
if (it = args.find("clientchallenge"); it != std::end(args)) {
auto challenge = util::from_hex_vec(it->second, true);
clientchallenge(sess_it->second, tree, challenge);
} }
else if (it = args.find("serverchallengeresp"); it != std::end(args)) { else if (it = args.find("serverchallengeresp"); it != std::end(args)) {
serverchallengeresp(sess_it->second, tree, args); auto encrypted_response = util::from_hex_vec(it->second, true);
serverchallengeresp(sess_it->second, tree, encrypted_response);
} }
else if (it = args.find("clientpairingsecret"); it != std::end(args)) { else if (it = args.find("clientpairingsecret"); it != std::end(args)) {
clientpairingsecret(sess_it->second, tree, args); auto pairingsecret = util::from_hex_vec(it->second, true);
clientpairingsecret(sess_it->second, tree, pairingsecret);
} }
else { else {
tree.put("root.<xmlattr>.status_code", 404); tree.put("root.<xmlattr>.status_code", 404);
@@ -1352,6 +1370,12 @@ namespace nvhttp {
return; return;
} }
void
setup(const std::string &pkey, const std::string &cert) {
conf_intern.pkey = pkey;
conf_intern.servercert = cert;
}
void void
start() { start() {
auto shutdown_event = mail::man->event<bool>(mail::shutdown); auto shutdown_event = mail::man->event<bool>(mail::shutdown);
@@ -1366,8 +1390,9 @@ namespace nvhttp {
load_state(); load_state();
} }
conf_intern.pkey = file_handler::read_file(config::nvhttp.pkey.c_str()); auto pkey = file_handler::read_file(config::nvhttp.pkey.c_str());
conf_intern.servercert = file_handler::read_file(config::nvhttp.cert.c_str()); auto cert = file_handler::read_file(config::nvhttp.cert.c_str());
setup(pkey, cert);
// resume doesn't always get the parameter "localAudioPlayMode" // resume doesn't always get the parameter "localAudioPlayMode"
// launch will store it in host_audio // launch will store it in host_audio

View File

@@ -66,6 +66,123 @@ namespace nvhttp {
std::shared_ptr<rtsp_stream::launch_session_t> 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); 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, std::shared_ptr<safe::queue_t<crypto::x509_t>> &add_cert, boost::property_tree::ptree &tree, const std::string &client_pairing_secret);
/** /**
* @brief Compare the user supplied pin to the Moonlight pin. * @brief Compare the user supplied pin to the Moonlight pin.
* @param pin The user supplied pin. * @param pin The user supplied pin.

View File

@@ -41,6 +41,7 @@
// local includes // local includes
#include "config.h" #include "config.h"
#include "confighttp.h" #include "confighttp.h"
#include "display_device.h"
#include "logging.h" #include "logging.h"
#include "platform/common.h" #include "platform/common.h"
#include "process.h" #include "process.h"
@@ -66,6 +67,13 @@ namespace system_tray {
proc::proc.terminate(); proc::proc.terminate();
} }
void
tray_reset_display_device_config_cb(struct tray_menu *item) {
BOOST_LOG(info) << "Resetting display device config from system tray"sv;
std::ignore = display_device::reset_persistence();
}
void void
tray_restart_cb(struct tray_menu *item) { tray_restart_cb(struct tray_menu *item) {
BOOST_LOG(info) << "Restarting from system tray"sv; BOOST_LOG(info) << "Restarting from system tray"sv;
@@ -109,6 +117,10 @@ namespace system_tray {
// { .text = nullptr } } }, // { .text = nullptr } } },
// { .text = "-" }, // { .text = "-" },
{ .text = TRAY_MSG_NO_APP_RUNNING, .cb = tray_force_stop_cb }, { .text = TRAY_MSG_NO_APP_RUNNING, .cb = tray_force_stop_cb },
// Currently display device settings are only supported on Windows
#ifdef _WIN32
{ .text = "Reset Display Device Config", .cb = tray_reset_display_device_config_cb },
#endif
{ .text = "Restart", .cb = tray_restart_cb }, { .text = "Restart", .cb = tray_restart_cb },
{ .text = "Quit", .cb = tray_quit_cb }, { .text = "Quit", .cb = tray_quit_cb },
{ .text = nullptr } }, { .text = nullptr } },

View File

@@ -19,6 +19,13 @@ namespace system_tray {
void void
tray_force_stop_cb(struct tray_menu *item); tray_force_stop_cb(struct tray_menu *item);
/**
* @brief Callback for resetting display device configuration.
* @param item The tray menu item.
*/
void
tray_reset_display_device_config_cb(struct tray_menu *item);
/** /**
* @brief Callback for restarting Sunshine from the system tray. * @brief Callback for restarting Sunshine from the system tray.
* @param item The tray menu item. * @param item The tray menu item.

View File

@@ -22,6 +22,10 @@ const props = defineProps({
type: String, type: String,
default: "missing-prefix" default: "missing-prefix"
}, },
inverseValues: {
type: Boolean,
default: false,
},
default: { default: {
type: undefined, type: undefined,
default: null, default: null,
@@ -79,7 +83,9 @@ const checkboxValues = (() => {
return ["true", "false"]; return ["true", "false"];
})(); })();
return { truthy: mappedValues[0], falsy: mappedValues[1] }; const truthyIndex = props.inverseValues ? 1 : 0;
const falsyIndex = props.inverseValues ? 0 : 1;
return { truthy: mappedValues[truthyIndex], falsy: mappedValues[falsyIndex] };
})(); })();
const parsedDefaultPropValue = (() => { const parsedDefaultPropValue = (() => {
const boolValues = mapToBoolRepresentation(props.default); const boolValues = mapToBoolRepresentation(props.default);

View File

@@ -157,6 +157,7 @@
desc="apps.global_prep_desc" desc="apps.global_prep_desc"
v-model="editForm['exclude-global-prep-cmd']" v-model="editForm['exclude-global-prep-cmd']"
default="true" default="true"
inverse-values
></Checkbox> ></Checkbox>
<div class="mb-3"> <div class="mb-3">
<label for="appName" class="form-label">{{ $t('apps.cmd_prep_name') }}</label> <label for="appName" class="form-label">{{ $t('apps.cmd_prep_name') }}</label>

View File

@@ -51,12 +51,12 @@
"env_app_name": "Nom de l'application", "env_app_name": "Nom de l'application",
"env_client_audio_config": "La configuration audio demandée par le client (2.0/5.1/7.1)", "env_client_audio_config": "La configuration audio demandée par le client (2.0/5.1/7.1)",
"env_client_enable_sops": "Le client a activé l'option pour optimiser le jeu pour une diffusion optimale (true/false)", "env_client_enable_sops": "Le client a activé l'option pour optimiser le jeu pour une diffusion optimale (true/false)",
"env_client_fps": "FPS demandé par le client (int)", "env_client_fps": "FPS demandé par le client (entier)",
"env_client_gcmap": "Le masque de manette demandé, au format bitset/bitfield (int)", "env_client_gcmap": "Le masque de manette demandé, au format bitset/bitfield (entier)",
"env_client_hdr": "Le HDR est activé par le client (true/false)", "env_client_hdr": "Le HDR est activé par le client (true/false)",
"env_client_height": "La hauteur demandée par le client (int)", "env_client_height": "La hauteur demandée par le client (entier)",
"env_client_host_audio": "Le client a activé l'audio côté audio (true/false)", "env_client_host_audio": "Le client a activé l'audio côté audio (true/false)",
"env_client_width": "La largeur demandée par le client (int)", "env_client_width": "La largeur demandée par le client (entier)",
"env_displayplacer_example": "Exemple - displayplacer pour l'automatisation de la résolution :", "env_displayplacer_example": "Exemple - displayplacer pour l'automatisation de la résolution :",
"env_qres_example": "Exemple - QRes pour l'automatisation de la résolution :", "env_qres_example": "Exemple - QRes pour l'automatisation de la résolution :",
"env_qres_path": "chemin de qres", "env_qres_path": "chemin de qres",
@@ -112,8 +112,8 @@
"amd_rc_cqp": "cqp -- mode constant qp", "amd_rc_cqp": "cqp -- mode constant qp",
"amd_rc_desc": "Ceci contrôle la méthode de contrôle du débit pour s'assurer que nous ne dépassons pas la cible du bitrate client. 'cqp' n'est pas adapté pour le ciblage de débit, et d'autres options en plus de 'vbr_latency' dépendent de HRD Enforcement pour aider à limiter les débordements de débit.", "amd_rc_desc": "Ceci contrôle la méthode de contrôle du débit pour s'assurer que nous ne dépassons pas la cible du bitrate client. 'cqp' n'est pas adapté pour le ciblage de débit, et d'autres options en plus de 'vbr_latency' dépendent de HRD Enforcement pour aider à limiter les débordements de débit.",
"amd_rc_group": "Réglages de contrôle du débit AMF", "amd_rc_group": "Réglages de contrôle du débit AMF",
"amd_rc_vbr_latency": "vbr_latency -- Débit variable limité de latence (par défaut)", "amd_rc_vbr_latency": "vbr_latency -- débit variable limité de latence (par défaut)",
"amd_rc_vbr_peak": "vbr_peak -- Débit variable contraint par le pic", "amd_rc_vbr_peak": "vbr_peak -- débit variable contraint par le pic",
"amd_usage": "Utilisation de l'AMF", "amd_usage": "Utilisation de l'AMF",
"amd_usage_desc": "Définit le profil d'encodage de base. Toutes les options présentées ci-dessous remplaceront un sous-ensemble du profil d'utilisation, mais il y a d'autres paramètres cachés qui ne peuvent pas être configurés ailleurs.", "amd_usage_desc": "Définit le profil d'encodage de base. Toutes les options présentées ci-dessous remplaceront un sous-ensemble du profil d'utilisation, mais il y a d'autres paramètres cachés qui ne peuvent pas être configurés ailleurs.",
"amd_usage_lowlatency": "lowlatency - faible latence (rapide)", "amd_usage_lowlatency": "lowlatency - faible latence (rapide)",
@@ -145,7 +145,7 @@
"channels": "Nombre maximum de clients connectés", "channels": "Nombre maximum de clients connectés",
"channels_desc_1": "Apollo peut permettre à une seule session de streaming d'être partagée simultanément avec plusieurs clients.", "channels_desc_1": "Apollo peut permettre à une seule session de streaming d'être partagée simultanément avec plusieurs clients.",
"channels_desc_2": "Certains encodeurs matériels peuvent avoir des limitations qui réduisent les performances avec plusieurs flux.", "channels_desc_2": "Certains encodeurs matériels peuvent avoir des limitations qui réduisent les performances avec plusieurs flux.",
"coder_cabac": "cabac -- Contexte de codage arithmétique binaire adaptatif - qualité supérieure", "coder_cabac": "cabac -- contexte de codage arithmétique binaire adaptatif - qualité supérieure",
"coder_cavlc": "cavlc -- codage de la durée adaptative du contexte - décodage plus rapide", "coder_cavlc": "cavlc -- codage de la durée adaptative du contexte - décodage plus rapide",
"configuration": "Configuration", "configuration": "Configuration",
"controller": "Activer l'entrée manette", "controller": "Activer l'entrée manette",
@@ -205,7 +205,7 @@
"file_state_desc": "Le fichier où l'état actuel de Apollo est stocké", "file_state_desc": "Le fichier où l'état actuel de Apollo est stocké",
"fps": "FPS annoncés", "fps": "FPS annoncés",
"gamepad": "Type de manette émulée", "gamepad": "Type de manette émulée",
"gamepad_auto": "Options de la sélection automatique", "gamepad_auto": "Options de sélection automatique",
"gamepad_desc": "Choisissez le type de manette à émuler sur l'hôte", "gamepad_desc": "Choisissez le type de manette à émuler sur l'hôte",
"gamepad_ds4": "DS4 (PS4)", "gamepad_ds4": "DS4 (PS4)",
"gamepad_ds4_manual": "Options de sélection DS4", "gamepad_ds4_manual": "Options de sélection DS4",
@@ -434,14 +434,14 @@
"restart_apollo_desc": "Si Apollo ne fonctionne pas correctement, vous pouvez essayer de le redémarrer. Cela mettra fin à toutes les sessions en cours.", "restart_apollo_desc": "Si Apollo ne fonctionne pas correctement, vous pouvez essayer de le redémarrer. Cela mettra fin à toutes les sessions en cours.",
"restart_apollo_success": "Apollo redémarre", "restart_apollo_success": "Apollo redémarre",
"troubleshooting": "Dépannage", "troubleshooting": "Dépannage",
"unpair_all": "Désappairer tous les appareils", "unpair_all": "Dissocier tous les périphériques",
"unpair_all_error": "Erreur lors de la dissociation", "unpair_all_error": "Erreur lors de la dissociation",
"unpair_all_success": "Désappairage réussi.", "unpair_all_success": "Désappairage réussi.",
"unpair_desc": "Retirer vos appareils appariés. Les appareils individuellement non appariés avec une session active resteront connectés, mais ne peuvent pas démarrer ou reprendre une session.", "unpair_desc": "Supprimez vos périphériques appairés. Les périphériques dissociés individuellement avec une session active resteront connectés, mais ne pourront pas démarrer ou reprendre une session.",
"unpair_single_no_devices": "Il n'y a aucun appareil associé.", "unpair_single_no_devices": "Il n'y a aucun appareil associé.",
"unpair_single_success": "Cependant, le(s) appareil(s) peuvent toujours être dans une session active. Utilisez le bouton 'Forcer la fermeture' ci-dessus pour mettre fin à toute session ouverte.", "unpair_single_success": "Cependant, le(s) appareil(s) peuvent toujours être dans une session active. Utilisez le bouton 'Forcer la fermeture' ci-dessus pour mettre fin à toute session ouverte.",
"unpair_single_unknown": "Client inconnu", "unpair_single_unknown": "Client inconnu",
"unpair_title": "Désappairer les appareils" "unpair_title": "Dissocier les périphériques"
}, },
"welcome": { "welcome": {
"confirm_password": "Confirmation du mot de passe", "confirm_password": "Confirmation du mot de passe",

View File

@@ -45,6 +45,10 @@ file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS
set(SUNSHINE_SOURCES set(SUNSHINE_SOURCES
${SUNSHINE_TARGET_FILES}) ${SUNSHINE_TARGET_FILES})
# copy fixtures to build directory
file(COPY ${CMAKE_SOURCE_DIR}/tests/fixtures/unit
DESTINATION ${CMAKE_BINARY_DIR}/tests/fixtures)
# remove main.cpp from the list of sources # remove main.cpp from the list of sources
list(REMOVE_ITEM SUNSHINE_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp) list(REMOVE_ITEM SUNSHINE_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp)

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLePNlWN06FLlM
ujWzIX8UICO7SWfH5DXlafVjpxwi/WCkdO6FxixqRNGu71wMvJXFbDlNR8fqX2xo
+eq17J3uFKn+qdjmP3L38bkqxhoJ/nCrXkeGyCTQ+Daug63ZYSJeW2Mmf+LAR5/i
/fWYfXpSlbcf5XJQPEWvENpLqWu+NOU50dJXIEVYpUXRx2+x4ZbwkH7tVJm94L+C
OUyiJKQPyWgU2aFsyJGwHFfePfSUpfYHqbHZV/ILpY59VJairBwE99bx/mBvMI7a
hBmJTSDuDffJcPDhFF5kZa0UkQPrPvhXcQaSRti7v0VonEQj8pTSnGYr9ktWKk92
wxDyn9S3AgMBAAECggEAbEhQ14WELg2rUz7hpxPTaiV0fo4hEcrMN+u8sKzVF3Xa
QYsNCNoe9urq3/r39LtDxU3D7PGfXYYszmz50Jk8ruAGW8WN7XKkv3i/fxjv8JOc
6EYDMKJAnYkKqLLhCQddX/Oof2udg5BacVWPpvhX6a1NSEc2H6cDupfwZEWkVhMi
bCC3JcNmjFa8N7ow1/5VQiYVTjpxfV7GY1GRe7vMvBucdQKH3tUG5PYXKXytXw/j
KDLaECiYVT89KbApkI0zhy7I5g3LRq0Rs5fmYLCjVebbuAL1W5CJHFJeFOgMKvnO
QSl7MfHkTnzTzUqwkwXjgNMGsTosV4UloL9gXVF6GQKBgQD5fI771WETkpaKjWBe
6XUVSS98IOAPbTGpb8CIhSjzCuztNAJ+0ey1zklQHonMFbdmcWTkTJoF3ECqAos9
vxB4ROg+TdqGDcRrXa7Twtmhv66QvYxttkaK3CqoLX8CCTnjgXBCijo6sCpo6H1T
+y55bBDpxZjNFT5BV3+YPBfWQwKBgQDQyNt+saTqJqxGYV7zWQtOqKORRHAjaJpy
m5035pky5wORsaxQY8HxbsTIQp9jBSw3SQHLHN/NAXDl2k7VAw/axMc+lj9eW+3z
2Hv5LVgj37jnJYEpYwehvtR0B4jZnXLyLwShoBdRPkGlC5fs9+oWjQZoDwMLZfTg
eZVOJm6SfQKBgQDfxYcB/kuKIKsCLvhHaSJpKzF6JoqRi6FFlkScrsMh66TCxSmP
0n58O0Cqqhlyge/z5LVXyBVGOF2Pn6SAh4UgOr4MVAwyvNp2aprKuTQ2zhSnIjx4
k0sGdZ+VJOmMS/YuRwUHya+cwDHp0s3Gq77tja5F38PD/s/OD8sUIqJGvQKBgBfI
6ghy4GC0ayfRa+m5GSqq14dzDntaLU4lIDIAGS/NVYDBhunZk3yXq99Mh6/WJQVf
Uc77yRsnsN7ekeB+as33YONmZm2vd1oyLV1jpwjfMcdTZHV8jKAGh1l4ikSQRUoF
xTdMb5uXxg6xVWtvisFq63HrU+N2iAESmMnAYxRZAoGAVEFJRRjPrSIUTCCKRiTE
br+cHqy6S5iYRxGl9riKySBKeU16fqUACIvUqmqlx4Secj3/Hn/VzYEzkxcSPwGi
qMgdS0R+tacca7NopUYaaluneKYdS++DNlT/m+KVHqLynQr54z1qBlThg9KGrpmM
LGZkXtQpx6sX7v3Kq56PkNk=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6zCCAdOgAwIBAgIBATANBgkqhkiG9w0BAQsFADA5MQswCQYDVQQGEwJJVDEW
MBQGA1UECgwNR2FtZXNPbldoYWxlczESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIy
MDQwOTA5MTYwNVoXDTQyMDQwNDA5MTYwNVowOTELMAkGA1UEBhMCSVQxFjAUBgNV
BAoMDUdhbWVzT25XaGFsZXMxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMt482VY3ToUuUy6NbMhfxQgI7tJZ8fkNeVp
9WOnHCL9YKR07oXGLGpE0a7vXAy8lcVsOU1Hx+pfbGj56rXsne4Uqf6p2OY/cvfx
uSrGGgn+cKteR4bIJND4Nq6DrdlhIl5bYyZ/4sBHn+L99Zh9elKVtx/lclA8Ra8Q
2kupa7405TnR0lcgRVilRdHHb7HhlvCQfu1Umb3gv4I5TKIkpA/JaBTZoWzIkbAc
V9499JSl9gepsdlX8guljn1UlqKsHAT31vH+YG8wjtqEGYlNIO4N98lw8OEUXmRl
rRSRA+s++FdxBpJG2Lu/RWicRCPylNKcZiv2S1YqT3bDEPKf1LcCAwEAATANBgkq
hkiG9w0BAQsFAAOCAQEAqPBqzvDjl89pZMll3Ge8RS7HeDuzgocrhOcT2jnk4ag7
/TROZuISjDp6+SnL3gPEt7E2OcFAczTg3l/wbT5PFb6vM96saLm4EP0zmLfK1FnM
JDRahKutP9rx6RO5OHqsUB+b4jA4W0L9UnXUoLKbjig501AUix0p52FBxu+HJ90r
HlLs3Vo6nj4Z/PZXrzaz8dtQ/KJMpd/g/9xlo6BKAnRk5SI8KLhO4hW6zG0QA56j
X4wnh1bwdiidqpcgyuKossLOPxbS786WmsesaAWPnpoY6M8aija+ALwNNuWWmyMg
9SVDV76xJzM36Uq7Kg3QJYTlY04WmPIdJHkCtXWf9g==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,210 @@
/**
* @file tests/unit/test_http_pairing.cpp
* @brief Test src/nvhttp.cpp HTTP pairing process
*/
#include <src/nvhttp.h>
#include "../tests_common.h"
#include "src/file_handler.h"
using namespace nvhttp;
struct pairing_input {
std::shared_ptr<pair_session_t> session;
/**
* Normally server challenge is generated by the server, but for testing purposes
* we can override it with a custom value. This way the process is deterministic.
*/
std::string override_server_challenge;
std::string pin;
std::string client_challenge;
std::string server_challenge_resp;
std::string client_pairing_secret;
};
struct pairing_output {
bool phase_1_success;
bool phase_2_success;
bool phase_3_success;
bool phase_4_success;
};
const auto PRIVATE_KEY = file_handler::read_file("fixtures/unit/pairing_test_key.pem");
const auto PUBLIC_CERT = file_handler::read_file("fixtures/unit/pairing_test_public.cert");
struct PairingTest: testing::TestWithParam<std::tuple<pairing_input, pairing_output>> {};
TEST_P(PairingTest, Run) {
auto [input, expected] = GetParam();
boost::property_tree::ptree tree;
setup(PRIVATE_KEY, PUBLIC_CERT);
// phase 1
getservercert(*input.session, tree, input.pin);
ASSERT_EQ(tree.get<int>("root.paired") == 1, expected.phase_1_success);
if (!expected.phase_1_success) {
return;
}
// phase 2
clientchallenge(*input.session, tree, input.client_challenge);
ASSERT_EQ(tree.get<int>("root.paired") == 1, expected.phase_2_success);
if (!expected.phase_2_success) {
return;
}
// phase 3
serverchallengeresp(*input.session, tree, input.server_challenge_resp);
ASSERT_EQ(tree.get<int>("root.paired") == 1, expected.phase_3_success);
if (!expected.phase_3_success) {
return;
}
input.session->serverchallenge = input.override_server_challenge;
// phase 4
auto input_client_cert = input.session->client.cert; // Will be moved
auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>(30);
clientpairingsecret(*input.session, add_cert, tree, input.client_pairing_secret);
ASSERT_EQ(tree.get<int>("root.paired") == 1, expected.phase_4_success);
// Check that we actually added the input client certificate to `add_cert`
if (expected.phase_4_success) {
ASSERT_EQ(add_cert->peek(), true);
auto cert = add_cert->pop();
char added_subject_name[256];
X509_NAME_oneline(X509_get_subject_name(cert.get()), added_subject_name, sizeof(added_subject_name));
auto input_cert = crypto::x509(input_client_cert);
char original_suject_name[256];
X509_NAME_oneline(X509_get_subject_name(input_cert.get()), original_suject_name, sizeof(original_suject_name));
ASSERT_EQ(std::string(added_subject_name), std::string(original_suject_name));
}
}
INSTANTIATE_TEST_SUITE_P(
TestWorkingPairing,
PairingTest,
testing::Values(
std::make_tuple(
pairing_input {
.session = std::make_shared<pair_session_t>(
pair_session_t {
.client = {
.uniqueID = "1234",
.cert = PUBLIC_CERT,
.name = "test" },
.async_insert_pin = { .salt = "ff5dc6eda99339a8a0793e216c4257c4" } }),
.override_server_challenge = util::from_hex_vec("AAAAAAAAAAAAAAAA", true),
.pin = "5338",
/* AES("CLIENT CHALLENGE") */
.client_challenge = util::from_hex_vec("741CD3D6890C16DA39D53BCA0893AAF0", true),
/* SHA = SHA265(server_challenge + public cert signature + "SECRET ") = "6493DAE49C913E1AEAF37C1072F71D664B72B2C4DA1FFB4720BECE0D929E008A"
* AES( SHA ) */
.server_challenge_resp = util::from_hex_vec("920BABAE9F7599AA1CA8EC87FB3454C91872A7D8D5127DDC176C2FDAE635CF7A", true),
/* secret + x509 signature */
.client_pairing_secret = util::from_hex_vec("000102030405060708090A0B0C0D0EFF" // secret
"9BB74D8DE2FF006C3F47FC45EFDAA97D433783AFAB3ACD85CA7ED2330BB2A7BD18A5B044AF8CAC177116FAE8A6E8E44653A8944A0F8EA138B2E013756D847D2C4FC52F736E2E7E9B4154712B18F8307B2A161E010F0587744163E42ECA9EA548FC435756EDCF1FEB94037631ABB72B29DDAC0EA5E61F2DBFCC3B20AA021473CC85AC98D88052CA6618ED1701EFBF142C18D5E779A3155B84DF65057D4823EC194E6DF14006793E8D7A3DCCE20A911636C4E01ECA8B54B9DE9F256F15DE9A980EA024B30D77579140D45EC220C738164BDEEEBF7364AE94A5FF9B784B40F2E640CE8603017DEEAC7B2AD77B807C643B7B349C110FE15F94C7B3D37FF15FDFBE26",
true) },
pairing_output { true, true, true, true }),
// Testing that when passing some empty values we aren't triggering any exception
std::make_tuple(pairing_input {
.session = std::make_shared<pair_session_t>(pair_session_t { .client = {}, .async_insert_pin = { .salt = "ff5dc6eda99339a8a0793e216c4257c4" } }),
.override_server_challenge = {},
.pin = {},
.client_challenge = {},
.server_challenge_resp = {},
.client_pairing_secret = util::from_hex_vec("000102030405060708090A0B0C0D0EFFxDEADBEEF", true),
},
// Only phase 4 will fail, when we check what has been exchanged
pairing_output { true, true, true, false }),
// Testing that when passing some empty values we aren't triggering any exception
std::make_tuple(pairing_input {
.session = std::make_shared<pair_session_t>(pair_session_t { .client = { .cert = PUBLIC_CERT }, .async_insert_pin = { .salt = "ff5dc6eda99339a8a0793e216c4257c4" } }),
.override_server_challenge = {},
.pin = {},
.client_challenge = {},
.server_challenge_resp = {},
.client_pairing_secret = util::from_hex_vec("000102030405060708090A0B0C0D0EFFxDEADBEEF", true),
},
// Only phase 4 will fail, when we check what has been exchanged
pairing_output { true, true, true, false })));
INSTANTIATE_TEST_SUITE_P(
TestFailingPairing,
PairingTest,
testing::Values(
/**
* Wrong PIN
*/
std::make_tuple(
pairing_input {
.session = std::make_shared<pair_session_t>(
pair_session_t {
.client = {
.uniqueID = "1234",
.cert = PUBLIC_CERT,
.name = "test" },
.async_insert_pin = { .salt = "ff5dc6eda99339a8a0793e216c4257c4" } }),
.override_server_challenge = util::from_hex_vec("AAAAAAAAAAAAAAAA", true),
.pin = "0000",
.client_challenge = util::from_hex_vec("741CD3D6890C16DA39D53BCA0893AAF0", true),
.server_challenge_resp = util::from_hex_vec("920BABAE9F7599AA1CA8EC87FB3454C91872A7D8D5127DDC176C2FDAE635CF7A", true),
.client_pairing_secret = util::from_hex_vec("000102030405060708090A0B0C0D0EFF" // secret
"9BB74D8DE2FF006C3F47FC45EFDAA97D433783AFAB3ACD85CA7ED2330BB2A7BD18A5B044AF8CAC177116FAE8A6E8E44653A8944A0F8EA138B2E013756D847D2C4FC52F736E2E7E9B4154712B18F8307B2A161E010F0587744163E42ECA9EA548FC435756EDCF1FEB94037631ABB72B29DDAC0EA5E61F2DBFCC3B20AA021473CC85AC98D88052CA6618ED1701EFBF142C18D5E779A3155B84DF65057D4823EC194E6DF14006793E8D7A3DCCE20A911636C4E01ECA8B54B9DE9F256F15DE9A980EA024B30D77579140D45EC220C738164BDEEEBF7364AE94A5FF9B784B40F2E640CE8603017DEEAC7B2AD77B807C643B7B349C110FE15F94C7B3D37FF15FDFBE26",
true) },
pairing_output { true, true, true, false }),
/**
* Wrong client challenge
*/
std::make_tuple(pairing_input { .session = std::make_shared<pair_session_t>(pair_session_t { .client = { .uniqueID = "1234", .cert = PUBLIC_CERT, .name = "test" }, .async_insert_pin = { .salt = "ff5dc6eda99339a8a0793e216c4257c4" } }), .override_server_challenge = util::from_hex_vec("AAAAAAAAAAAAAAAA", true), .pin = "5338", .client_challenge = util::from_hex_vec("741CD3D6890C16DA39D53BCA0893AAF0", true), .server_challenge_resp = util::from_hex_vec("WRONG", true),
.client_pairing_secret = util::from_hex_vec("000102030405060708090A0B0C0D0EFF" // secret
"9BB74D8DE2FF006C3F47FC45EFDAA97D433783AFAB3ACD85CA7ED2330BB2A7BD18A5B044AF8CAC177116FAE8A6E8E44653A8944A0F8EA138B2E013756D847D2C4FC52F736E2E7E9B4154712B18F8307B2A161E010F0587744163E42ECA9EA548FC435756EDCF1FEB94037631ABB72B29DDAC0EA5E61F2DBFCC3B20AA021473CC85AC98D88052CA6618ED1701EFBF142C18D5E779A3155B84DF65057D4823EC194E6DF14006793E8D7A3DCCE20A911636C4E01ECA8B54B9DE9F256F15DE9A980EA024B30D77579140D45EC220C738164BDEEEBF7364AE94A5FF9B784B40F2E640CE8603017DEEAC7B2AD77B807C643B7B349C110FE15F94C7B3D37FF15FDFBE26",
true) },
pairing_output { true, true, true, false }),
/**
* Wrong signature
*/
std::make_tuple(pairing_input { .session = std::make_shared<pair_session_t>(pair_session_t { .client = { .uniqueID = "1234", .cert = PUBLIC_CERT, .name = "test" }, .async_insert_pin = { .salt = "ff5dc6eda99339a8a0793e216c4257c4" } }), .override_server_challenge = util::from_hex_vec("AAAAAAAAAAAAAAAA", true), .pin = "5338", .client_challenge = util::from_hex_vec("741CD3D6890C16DA39D53BCA0893AAF0", true), .server_challenge_resp = util::from_hex_vec("920BABAE9F7599AA1CA8EC87FB3454C91872A7D8D5127DDC176C2FDAE635CF7A", true),
.client_pairing_secret = util::from_hex_vec("000102030405060708090A0B0C0D0EFF" // secret
"NOSIGNATURE", // Wrong signature
true) },
pairing_output { true, true, true, false }),
/**
* null values (phase 1)
*/
std::make_tuple(pairing_input { .session = std::make_shared<pair_session_t>() }, pairing_output { false }),
/**
* null values (phase 4, phase 2 and 3 have no reason to fail since we are running them in order)
*/
std::make_tuple(pairing_input { .session = std::make_shared<pair_session_t>(pair_session_t { .async_insert_pin = { .salt = "ff5dc6eda99339a8a0793e216c4257c4" } }) }, pairing_output { true, true, true, false })));
TEST(PairingTest, OutOfOrderCalls) {
boost::property_tree::ptree tree;
setup(PRIVATE_KEY, PUBLIC_CERT);
pair_session_t sess {};
clientchallenge(sess, tree, "test");
ASSERT_FALSE(tree.get<int>("root.paired") == 1);
serverchallengeresp(sess, tree, "test");
ASSERT_FALSE(tree.get<int>("root.paired") == 1);
auto add_cert = std::make_shared<safe::queue_t<crypto::x509_t>>(30);
clientpairingsecret(sess, add_cert, tree, "test");
ASSERT_FALSE(tree.get<int>("root.paired") == 1);
// This should work, it's the first time we call it
sess.async_insert_pin.salt = "ff5dc6eda99339a8a0793e216c4257c4";
getservercert(sess, tree, "test");
ASSERT_TRUE(tree.get<int>("root.paired") == 1);
// Calling it again should fail
getservercert(sess, tree, "test");
ASSERT_FALSE(tree.get<int>("root.paired") == 1);
}