Fully encrypt control data
This commit is contained in:
+1
-1
@@ -33,7 +33,7 @@
|
|||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
namespace nvhttp {
|
namespace nvhttp {
|
||||||
|
|
||||||
constexpr auto VERSION = "7.1.415.0";
|
constexpr auto VERSION = "7.1.431.0";
|
||||||
constexpr auto GFE_VERSION = "3.12.0.1";
|
constexpr auto GFE_VERSION = "3.12.0.1";
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|||||||
+106
-16
@@ -34,6 +34,7 @@ extern "C" {
|
|||||||
#define IDX_RUMBLE_DATA 6
|
#define IDX_RUMBLE_DATA 6
|
||||||
#define IDX_TERMINATION 7
|
#define IDX_TERMINATION 7
|
||||||
#define IDX_PERIODIC_PING 8
|
#define IDX_PERIODIC_PING 8
|
||||||
|
#define IDX_ENCRYPTED 9
|
||||||
|
|
||||||
static const short packetTypes[] = {
|
static const short packetTypes[] = {
|
||||||
0x0305, // Start A
|
0x0305, // Start A
|
||||||
@@ -45,6 +46,7 @@ static const short packetTypes[] = {
|
|||||||
0x010b, // Rumble data
|
0x010b, // Rumble data
|
||||||
0x0100, // Termination
|
0x0100, // Termination
|
||||||
0x0200, // Periodic Ping
|
0x0200, // Periodic Ping
|
||||||
|
0x0001, // fully encrypted
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace asio = boost::asio;
|
namespace asio = boost::asio;
|
||||||
@@ -82,6 +84,17 @@ struct audio_packet_raw_t {
|
|||||||
RTP_PACKET rtp;
|
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 {
|
struct audio_fec_packet_raw_t {
|
||||||
uint8_t *payload() {
|
uint8_t *payload() {
|
||||||
return (uint8_t *)(this + 1);
|
return (uint8_t *)(this + 1);
|
||||||
@@ -133,10 +146,21 @@ public:
|
|||||||
// Therefore, iterate is implemented further down the source file
|
// Therefore, iterate is implemented further down the source file
|
||||||
void iterate(std::chrono::milliseconds timeout);
|
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<void(session_t *, const std::string_view &)> cb) {
|
void map(uint16_t type, std::function<void(session_t *, const std::string_view &)> cb) {
|
||||||
_map_type_cb.emplace(type, std::move(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) {
|
void send(const std::string_view &payload) {
|
||||||
std::for_each(_host->peers, _host->peers + _host->peerCount, [payload](auto &peer) {
|
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);
|
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;
|
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) {
|
void control_server_t::iterate(std::chrono::milliseconds timeout) {
|
||||||
ENetEvent event;
|
ENetEvent event;
|
||||||
auto res = enet_host_service(_host.get(), &event, timeout.count());
|
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: {
|
case ENET_EVENT_TYPE_RECEIVE: {
|
||||||
net::packet_t packet { event.packet };
|
net::packet_t packet { event.packet };
|
||||||
|
|
||||||
auto type = (std::uint16_t *)packet->data;
|
auto type = *(std::uint16_t *)packet->data;
|
||||||
std::string_view payload { (char *)packet->data + sizeof(*type), packet->dataLength - sizeof(*type) };
|
std::string_view payload { (char *)packet->data + sizeof(type), packet->dataLength - sizeof(type) };
|
||||||
|
|
||||||
auto cb = _map_type_cb.find(*type);
|
call(type, session, payload);
|
||||||
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);
|
|
||||||
}
|
|
||||||
} break;
|
} break;
|
||||||
case ENET_EVENT_TYPE_CONNECT:
|
case ENET_EVENT_TYPE_CONNECT:
|
||||||
BOOST_LOG(info) << "CLIENT CONNECTED"sv;
|
BOOST_LOG(info) << "CLIENT CONNECTED"sv;
|
||||||
@@ -420,7 +448,9 @@ std::vector<uint8_t> replace(const std::string_view &original, const std::string
|
|||||||
}
|
}
|
||||||
|
|
||||||
void controlBroadcastThread(control_server_t *server) {
|
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) {
|
server->map(packetTypes[IDX_START_A], [&](session_t *session, const std::string_view &payload) {
|
||||||
BOOST_LOG(debug) << "type [IDX_START_A]"sv;
|
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) {
|
server->map(packetTypes[IDX_INPUT_DATA], [&](session_t *session, const std::string_view &payload) {
|
||||||
BOOST_LOG(debug) << "type [IDX_INPUT_DATA]"sv;
|
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 };
|
std::string_view tagged_cipher { payload.data() + sizeof(tagged_cipher_length), (size_t)tagged_cipher_length };
|
||||||
|
|
||||||
crypto::cipher_t cipher { session->gcm_key };
|
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;
|
BOOST_LOG(error) << "Failed to verify tag"sv;
|
||||||
|
|
||||||
session::stop(*session);
|
session::stop(*session);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(tagged_cipher_length >= 16 + session->iv.size()) {
|
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));
|
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<uint8_t> 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<bool>(mail::broadcast_shutdown);
|
auto shutdown_event = mail::man->event<bool>(mail::broadcast_shutdown);
|
||||||
while(!shutdown_event->peek()) {
|
while(!shutdown_event->peek()) {
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -881,7 +881,7 @@ struct endian_helper<T, std::enable_if_t<
|
|||||||
static inline T big(T x) {
|
static inline T big(T x) {
|
||||||
if(!x) return x;
|
if(!x) return x;
|
||||||
|
|
||||||
if constexpr(endianness<T>::big) {
|
if constexpr(endianness<T>::little) {
|
||||||
auto *data = reinterpret_cast<uint8_t *>(&*x);
|
auto *data = reinterpret_cast<uint8_t *>(&*x);
|
||||||
|
|
||||||
std::reverse(data, data + sizeof(*x));
|
std::reverse(data, data + sizeof(*x));
|
||||||
|
|||||||
Reference in New Issue
Block a user