/** * @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); }