diff --git a/sunshine/nvhttp.cpp b/sunshine/nvhttp.cpp index 66efae76..ed87884e 100644 --- a/sunshine/nvhttp.cpp +++ b/sunshine/nvhttp.cpp @@ -33,7 +33,7 @@ using namespace std::literals; namespace nvhttp { -constexpr auto VERSION = "7.1.415.0"; +constexpr auto VERSION = "7.1.431.0"; constexpr auto GFE_VERSION = "3.12.0.1"; namespace fs = std::filesystem; diff --git a/sunshine/stream.cpp b/sunshine/stream.cpp index 9fe35927..e329d639 100644 --- a/sunshine/stream.cpp +++ b/sunshine/stream.cpp @@ -34,6 +34,7 @@ extern "C" { #define IDX_RUMBLE_DATA 6 #define IDX_TERMINATION 7 #define IDX_PERIODIC_PING 8 +#define IDX_ENCRYPTED 9 static const short packetTypes[] = { 0x0305, // Start A @@ -45,6 +46,7 @@ static const short packetTypes[] = { 0x010b, // Rumble data 0x0100, // Termination 0x0200, // Periodic Ping + 0x0001, // fully encrypted }; namespace asio = boost::asio; @@ -82,6 +84,17 @@ struct audio_packet_raw_t { RTP_PACKET rtp; }; +typedef struct NVCTL_ENCRYPTED_PACKET_HEADER { + std::uint16_t encryptedHeaderType; // Always LE 0x0001 + std::uint16_t length; // sizeof(seq) + 16 byte tag + secondary header and data + std::uint32_t seq; // Monotonically increasing sequence number (used as IV for AES-GCM) + + uint8_t *payload() { + return (uint8_t *)(this + 1); + } + // encrypted NVCTL_ENET_PACKET_HEADER_V2 and payload data follow +} * PNVCTL_ENCRYPTED_PACKET_HEADER; + struct audio_fec_packet_raw_t { uint8_t *payload() { return (uint8_t *)(this + 1); @@ -133,10 +146,21 @@ public: // Therefore, iterate is implemented further down the source file void iterate(std::chrono::milliseconds timeout); + void call(std::uint16_t type, session_t *session, const std::string_view &payload); + void map(uint16_t type, std::function cb) { _map_type_cb.emplace(type, std::move(cb)); } + void send(const std::string_view &payload, net::peer_t peer) { + auto packet = enet_packet_create(payload.data(), payload.size(), ENET_PACKET_FLAG_RELIABLE); + if(enet_peer_send(peer, 0, packet)) { + enet_packet_destroy(packet); + } + + enet_host_flush(_host.get()); + } + void send(const std::string_view &payload) { std::for_each(_host->peers, _host->peers + _host->peerCount, [payload](auto &peer) { auto packet = enet_packet_create(payload.data(), payload.size(), ENET_PACKET_FLAG_RELIABLE); @@ -248,6 +272,21 @@ session_t *control_server_t::get_session(const net::peer_t peer) { return nullptr; } +void control_server_t::call(std::uint16_t type, session_t *session, const std::string_view &payload) { + auto cb = _map_type_cb.find(type); + if(cb == std::end(_map_type_cb)) { + BOOST_LOG(warning) + << "type [Unknown] { "sv << util::hex(type).to_string_view() << " }"sv << std::endl + << "---data---"sv << std::endl + << util::hex_vec(payload) << std::endl + << "---end data---"sv; + } + + else { + cb->second(session, payload); + } +} + void control_server_t::iterate(std::chrono::milliseconds timeout) { ENetEvent event; auto res = enet_host_service(_host.get(), &event, timeout.count()); @@ -267,21 +306,10 @@ void control_server_t::iterate(std::chrono::milliseconds timeout) { case ENET_EVENT_TYPE_RECEIVE: { net::packet_t packet { event.packet }; - auto type = (std::uint16_t *)packet->data; - std::string_view payload { (char *)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) }; + auto type = *(std::uint16_t *)packet->data; + std::string_view payload { (char *)packet->data + sizeof(type), packet->dataLength - sizeof(type) }; - auto cb = _map_type_cb.find(*type); - if(cb == std::end(_map_type_cb)) { - BOOST_LOG(warning) - << "type [Unknown] { "sv << util::hex(*type).to_string_view() << " }"sv << std::endl - << "---data---"sv << std::endl - << util::hex_vec(payload) << std::endl - << "---end data---"sv; - } - - else { - cb->second(session, payload); - } + call(type, session, payload); } break; case ENET_EVENT_TYPE_CONNECT: BOOST_LOG(info) << "CLIENT CONNECTED"sv; @@ -420,7 +448,9 @@ std::vector replace(const std::string_view &original, const std::string } void controlBroadcastThread(control_server_t *server) { - server->map(packetTypes[IDX_PERIODIC_PING], [](session_t *session, const std::string_view &payload) {}); + server->map(packetTypes[IDX_PERIODIC_PING], [](session_t *session, const std::string_view &payload) { + BOOST_LOG(verbose) << "type [IDX_START_A]"sv; + }); server->map(packetTypes[IDX_START_A], [&](session_t *session, const std::string_view &payload) { BOOST_LOG(debug) << "type [IDX_START_A]"sv; @@ -462,7 +492,7 @@ void controlBroadcastThread(control_server_t *server) { server->map(packetTypes[IDX_INPUT_DATA], [&](session_t *session, const std::string_view &payload) { BOOST_LOG(debug) << "type [IDX_INPUT_DATA]"sv; - int32_t tagged_cipher_length = util::endian::big(*(int32_t *)payload.data()); + auto tagged_cipher_length = util::endian::big(*(int32_t *)payload.data()); std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length }; crypto::cipher_t cipher { session->gcm_key }; @@ -475,6 +505,7 @@ void controlBroadcastThread(control_server_t *server) { BOOST_LOG(error) << "Failed to verify tag"sv; session::stop(*session); + return; } if(tagged_cipher_length >= 16 + session->iv.size()) { @@ -485,6 +516,65 @@ void controlBroadcastThread(control_server_t *server) { input::passthrough(session->input, std::move(plaintext)); }); + server->map(packetTypes[IDX_ENCRYPTED], [server](session_t *session, const std::string_view &payload) { + BOOST_LOG(verbose) << "type [IDX_ENCRYPTED]"sv; + + auto header = (PNVCTL_ENCRYPTED_PACKET_HEADER)(payload.data() - 2); + + auto length = util::endian::little(header->length); + auto seq = util::endian::little(header->seq); + + if(length < (16 + 4 + 4)) { + BOOST_LOG(warning) << "Control: Runt packet"sv; + return; + } + + auto tagged_cipher_length = length - 4; + std::string_view tagged_cipher { (char *)header->payload(), (size_t)tagged_cipher_length }; + + crypto::aes_t iv {}; + iv[0] = (char)seq; + + crypto::cipher_t cipher { session->gcm_key }; + cipher.padding = false; + + std::vector plaintext; + if(cipher.decrypt_gcm(iv, tagged_cipher, plaintext)) { + // something went wrong :( + + BOOST_LOG(error) << "Failed to verify tag"sv; + + session::stop(*session); + return; + } + + // Ensure compatibility with old packet type + std::string_view next_payload { (char *)plaintext.data(), plaintext.size() }; + auto type = *(std::uint16_t *)next_payload.data(); + + if(type == packetTypes[IDX_ENCRYPTED]) { + BOOST_LOG(error) << "Bad packet type [IDX_ENCRYPTED] found"sv; + + session::stop(*session); + return; + } + + // IDX_INPUT_DATA will attempt to decrypt unencrypted data, therefore we need to skip it. + if(type != packetTypes[IDX_INPUT_DATA]) { + server->call(type, session, next_payload); + + return; + } + + // Ensure compatibility with IDX_INPUT_DATA + constexpr auto skip = sizeof(std::uint16_t) * 2; + plaintext.erase(std::begin(plaintext), std::begin(plaintext) + skip); + + input::print(plaintext.data()); + input::passthrough(session->input, std::move(plaintext)); + }); + + auto shutdown_event = mail::man->event(mail::broadcast_shutdown); while(!shutdown_event->peek()) { { diff --git a/sunshine/utility.h b/sunshine/utility.h index d991bad4..b1e3a936 100644 --- a/sunshine/utility.h +++ b/sunshine/utility.h @@ -881,7 +881,7 @@ struct endian_helper::big) { + if constexpr(endianness::little) { auto *data = reinterpret_cast(&*x); std::reverse(data, data + sizeof(*x));