Merge commit from fork
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com> Co-authored-by: Cameron Gutman <2695644+cgutman@users.noreply.github.com>
This commit is contained in:
@@ -45,6 +45,10 @@ file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS
|
||||
set(SUNSHINE_SOURCES
|
||||
${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
|
||||
list(REMOVE_ITEM SUNSHINE_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp)
|
||||
|
||||
|
||||
28
tests/fixtures/unit/pairing_test_key.pem
vendored
Normal file
28
tests/fixtures/unit/pairing_test_key.pem
vendored
Normal 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-----
|
||||
18
tests/fixtures/unit/pairing_test_public.cert
vendored
Normal file
18
tests/fixtures/unit/pairing_test_public.cert
vendored
Normal 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-----
|
||||
210
tests/unit/test_http_pairing.cpp
Normal file
210
tests/unit/test_http_pairing.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user