From 89f097ae65277d42b5d40163d09d92e412e6d7dd Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sat, 18 Jan 2025 04:17:13 +0000 Subject: [PATCH] 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> --- src/nvhttp.cpp | 186 +++++++++------- src/nvhttp.h | 119 +++++++++++ tests/CMakeLists.txt | 4 + tests/fixtures/unit/pairing_test_key.pem | 28 +++ tests/fixtures/unit/pairing_test_public.cert | 18 ++ tests/unit/test_http_pairing.cpp | 210 +++++++++++++++++++ 6 files changed, 484 insertions(+), 81 deletions(-) create mode 100644 tests/fixtures/unit/pairing_test_key.pem create mode 100644 tests/fixtures/unit/pairing_test_public.cert create mode 100644 tests/unit/test_http_pairing.cpp diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp index e510d0837e6..83505310b5e 100644 --- a/src/nvhttp.cpp +++ b/src/nvhttp.cpp @@ -11,7 +11,6 @@ // lib includes #include -#include #include #include #include @@ -21,7 +20,6 @@ // local includes #include "config.h" -#include "crypto.h" #include "display_device.h" #include "file_handler.h" #include "globals.h" @@ -45,18 +43,6 @@ namespace nvhttp { crypto::cert_chain_t cert_chain; - 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 { public: SunshineHTTPSServer(const std::string &certification_file, const std::string &private_key_file): @@ -146,28 +132,6 @@ namespace nvhttp { std::vector named_devices; }; - struct pair_session_t { - struct { - std::string uniqueID; - std::string cert; - std::string name; - } client; - - std::unique_ptr cipher_key; - std::vector clienthash; - - std::string serversecret; - std::string serverchallenge; - - struct { - util::Either< - std::shared_ptr::Response>, - std::shared_ptr::Response>> - response; - std::string salt; - } async_insert_pin; - }; - // uniqueID, session std::unordered_map map_id_sess; client_t client_root; @@ -367,12 +331,29 @@ namespace nvhttp { 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..status_code", 400); + tree.put("root..status_message", status_msg); + remove_session(sess); // Security measure, delete the session when something went wrong and force a re-pair + } + void 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) { - tree.put("root.paired", 0); - tree.put("root..status_code", 400); - tree.put("root..status_message", "Salt too short"); + fail_pair(sess, tree, "Salt too short"); return; } @@ -389,30 +370,17 @@ namespace nvhttp { } void - serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const args_t &args) { - auto encrypted_response = util::from_hex_vec(get_arg(args, "serverchallengeresp"), true); - - std::vector decrypted; - 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..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); + clientchallenge(pair_session_t &sess, pt::ptree &tree, const std::string &challenge) { + if (sess.last_phase != PAIR_PHASE::GETSERVERCERT) { + fail_pair(sess, tree, "Out of order call to clientchallenge"); + return; + } + sess.last_phase = PAIR_PHASE::CLIENTCHALLENGE; + if (!sess.cipher_key) { + fail_pair(sess, tree, "Cipher key not set"); + return; + } crypto::cipher::ecb_t cipher(*sess.cipher_key, false); std::vector decrypted; @@ -446,21 +414,58 @@ namespace nvhttp { } void - clientpairingsecret(std::shared_ptr> &add_cert, pair_session_t &sess, pt::ptree &tree, const args_t &args) { + serverchallengeresp(pair_session_t &sess, pt::ptree &tree, const std::string &encrypted_response) { + if (sess.last_phase != PAIR_PHASE::CLIENTCHALLENGE) { + fail_pair(sess, tree, "Out of order call to serverchallengeresp"); + return; + } + sess.last_phase = PAIR_PHASE::SERVERCHALLENGERESP; + + if (!sess.cipher_key || sess.serversecret.empty()) { + fail_pair(sess, tree, "Cipher key or serversecret not set"); + return; + } + + std::vector decrypted; + 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..status_code", 200); + } + + void + clientpairingsecret(pair_session_t &sess, std::shared_ptr> &add_cert, 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; - auto pairingsecret = util::from_hex_vec(get_arg(args, "clientpairingsecret"), true); - if (pairingsecret.size() <= 16) { - tree.put("root.paired", 0); - tree.put("root..status_code", 400); - tree.put("root..status_message", "Clientpairingsecret too short"); + if (client_pairing_secret.size() <= 16) { + fail_pair(sess, tree, "Client pairing secret too short"); return; } - std::string_view secret { pairingsecret.data(), 16 }; - std::string_view sign { pairingsecret.data() + secret.size(), pairingsecret.size() - secret.size() }; + 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); + if (!x509) { + fail_pair(sess, tree, "Invalid client certificate"); + return; + } auto x509_sign = crypto::signature(x509); std::string data; @@ -473,20 +478,20 @@ namespace nvhttp { auto hash = crypto::hash(data); // 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); add_cert->raise(crypto::x509(client.cert)); // The client is now successfully paired and will be authorized to connect - auto it = map_id_sess.find(client.uniqueID); add_authorized_client(client.name, std::move(client.cert)); - map_id_sess.erase(it); } else { - map_id_sess.erase(client.uniqueID); tree.put("root.paired", 0); } + remove_session(sess); tree.put("root..status_code", 200); } @@ -568,7 +573,6 @@ namespace nvhttp { } auto uniqID { get_arg(args, "uniqueid") }; - auto sess_it = map_id_sess.find(uniqID); args_t::const_iterator it; if (it = args.find("phrase"); it != std::end(args)) { @@ -603,16 +607,29 @@ namespace nvhttp { else if (it->second == "pairchallenge"sv) { tree.put("root.paired", 1); tree.put("root..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..status_code", 400); + tree.put("root..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)) { - 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)) { - clientpairingsecret(add_cert, sess_it->second, tree, args); + auto pairingsecret = util::from_hex_vec(it->second, true); + clientpairingsecret(sess_it->second, add_cert, tree, pairingsecret); } else { tree.put("root..status_code", 404); @@ -1030,6 +1047,12 @@ namespace nvhttp { response->close_connection_after_response = true; } + void + setup(const std::string &pkey, const std::string &cert) { + conf_intern.pkey = pkey; + conf_intern.servercert = cert; + } + void start() { auto shutdown_event = mail::man->event(mail::shutdown); @@ -1044,8 +1067,9 @@ namespace nvhttp { load_state(); } - conf_intern.pkey = file_handler::read_file(config::nvhttp.pkey.c_str()); - conf_intern.servercert = file_handler::read_file(config::nvhttp.cert.c_str()); + auto pkey = file_handler::read_file(config::nvhttp.pkey.c_str()); + auto cert = file_handler::read_file(config::nvhttp.cert.c_str()); + setup(pkey, cert); auto add_cert = std::make_shared>(30); diff --git a/src/nvhttp.h b/src/nvhttp.h index 1f8726c35e0..e3af8a26192 100644 --- a/src/nvhttp.h +++ b/src/nvhttp.h @@ -9,9 +9,11 @@ #include // lib includes +#include #include // local includes +#include "crypto.h" #include "thread_safe.h" /** @@ -50,6 +52,123 @@ namespace nvhttp { void start(); + /** + * @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 cipher_key = {}; + std::vector clienthash = {}; + + std::string serversecret = {}; + std::string serverchallenge = {}; + + struct { + util::Either< + std::shared_ptr::Response>, + std::shared_ptr::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> &add_cert, 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. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 927c12b71be..0381ea3613e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/fixtures/unit/pairing_test_key.pem b/tests/fixtures/unit/pairing_test_key.pem new file mode 100644 index 00000000000..1c6e2ffe818 --- /dev/null +++ b/tests/fixtures/unit/pairing_test_key.pem @@ -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----- \ No newline at end of file diff --git a/tests/fixtures/unit/pairing_test_public.cert b/tests/fixtures/unit/pairing_test_public.cert new file mode 100644 index 00000000000..1350105d02f --- /dev/null +++ b/tests/fixtures/unit/pairing_test_public.cert @@ -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----- \ No newline at end of file diff --git a/tests/unit/test_http_pairing.cpp b/tests/unit/test_http_pairing.cpp new file mode 100644 index 00000000000..7b355e04495 --- /dev/null +++ b/tests/unit/test_http_pairing.cpp @@ -0,0 +1,210 @@ +/** + * @file tests/unit/test_http_pairing.cpp + * @brief Test src/nvhttp.cpp HTTP pairing process + */ + +#include + +#include "../tests_common.h" +#include "src/file_handler.h" + +using namespace nvhttp; + +struct pairing_input { + std::shared_ptr 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> {}; + +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("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("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("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>(30); + clientpairingsecret(*input.session, add_cert, tree, input.client_pairing_secret); + ASSERT_EQ(tree.get("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 { + .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 { .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 { .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 { + .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 { .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 { .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() }, 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 { .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("root.paired") == 1); + + serverchallengeresp(sess, tree, "test"); + ASSERT_FALSE(tree.get("root.paired") == 1); + + auto add_cert = std::make_shared>(30); + clientpairingsecret(sess, add_cert, tree, "test"); + ASSERT_FALSE(tree.get("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("root.paired") == 1); + + // Calling it again should fail + getservercert(sess, tree, "test"); + ASSERT_FALSE(tree.get("root.paired") == 1); +}