From c396e3c6e5a9c128499919477ff952737bdf5802 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Wed, 31 Oct 2018 23:07:03 +0800
Subject: [PATCH] network: check Console ID conflicts

As Console ID can be sensitive data sometimes, this implementation sent a SHA256 hash of it instead.
---
 externals/cpp-jwt                           |  1 +
 src/citra/citra.cpp                         |  4 +-
 src/citra_qt/multiplayer/direct_connect.cpp |  2 +
 src/citra_qt/multiplayer/host_room.cpp      |  6 ++-
 src/citra_qt/multiplayer/lobby.cpp          |  4 +-
 src/citra_qt/multiplayer/message.cpp        |  3 ++
 src/citra_qt/multiplayer/message.h          |  1 +
 src/citra_qt/multiplayer/state.cpp          |  3 ++
 src/core/hle/service/cfg/cfg.cpp            | 17 ++++++++
 src/core/hle/service/cfg/cfg.h              |  3 ++
 src/network/room.cpp                        | 48 +++++++++++++++++++--
 src/network/room.h                          |  1 +
 src/network/room_member.cpp                 | 16 ++++---
 src/network/room_member.h                   | 23 +++++-----
 14 files changed, 109 insertions(+), 23 deletions(-)
 create mode 160000 externals/cpp-jwt

diff --git a/externals/cpp-jwt b/externals/cpp-jwt
new file mode 160000
index 000000000..6e27aa4c8
--- /dev/null
+++ b/externals/cpp-jwt
@@ -0,0 +1 @@
+Subproject commit 6e27aa4c8671e183f11e327a2e1f556c64fdc4a9
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index b64510957..d09a6ddc1 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -39,6 +39,7 @@
 #include "core/frontend/applets/default_applets.h"
 #include "core/gdbstub/gdbstub.h"
 #include "core/hle/service/am/am.h"
+#include "core/hle/service/cfg/cfg.h"
 #include "core/loader/loader.h"
 #include "core/movie.h"
 #include "core/settings.h"
@@ -336,7 +337,8 @@ int main(int argc, char** argv) {
             member->BindOnStateChanged(OnStateChanged);
             LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
                       nickname);
-            member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredMac, password);
+            member->Join(nickname, Service::CFG::GetConsoleIdHash(system), address.c_str(), port, 0,
+                         Network::NoPreferredMac, password);
         } else {
             LOG_ERROR(Network, "Could not access RoomMember");
             return 0;
diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp
index 2d6e28c91..3870c1e25 100644
--- a/src/citra_qt/multiplayer/direct_connect.cpp
+++ b/src/citra_qt/multiplayer/direct_connect.cpp
@@ -15,6 +15,7 @@
 #include "citra_qt/multiplayer/state.h"
 #include "citra_qt/multiplayer/validation.h"
 #include "citra_qt/ui_settings.h"
+#include "core/hle/service/cfg/cfg.h"
 #include "core/settings.h"
 #include "network/network.h"
 #include "ui_direct_connect.h"
@@ -97,6 +98,7 @@ void DirectConnectWindow::Connect() {
         if (auto room_member = Network::GetRoomMember().lock()) {
             auto port = UISettings::values.port.toUInt();
             room_member->Join(ui->nickname->text().toStdString(),
+                              Service::CFG::GetConsoleIdHash(Core::System::GetInstance()),
                               ui->ip->text().toStdString().c_str(), port, 0,
                               Network::NoPreferredMac, ui->password->text().toStdString().c_str());
         }
diff --git a/src/citra_qt/multiplayer/host_room.cpp b/src/citra_qt/multiplayer/host_room.cpp
index 1d389539d..fa335bfae 100644
--- a/src/citra_qt/multiplayer/host_room.cpp
+++ b/src/citra_qt/multiplayer/host_room.cpp
@@ -19,6 +19,7 @@
 #include "citra_qt/ui_settings.h"
 #include "common/logging/log.h"
 #include "core/announce_multiplayer_session.h"
+#include "core/hle/service/cfg/cfg.h"
 #include "core/settings.h"
 #include "ui_host_room.h"
 
@@ -116,8 +117,9 @@ void HostRoomWindow::Host() {
                 return;
             }
         }
-        member->Join(ui->username->text().toStdString(), "127.0.0.1", port, 0,
-                     Network::NoPreferredMac, password);
+        member->Join(ui->username->text().toStdString(),
+                     Service::CFG::GetConsoleIdHash(Core::System::GetInstance()), "127.0.0.1", port,
+                     0, Network::NoPreferredMac, password);
 
         // Store settings
         UISettings::values.room_nickname = ui->username->text();
diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp
index 8a5630289..6b22c277c 100644
--- a/src/citra_qt/multiplayer/lobby.cpp
+++ b/src/citra_qt/multiplayer/lobby.cpp
@@ -15,6 +15,7 @@
 #include "citra_qt/multiplayer/validation.h"
 #include "citra_qt/ui_settings.h"
 #include "common/logging/log.h"
+#include "core/hle/service/cfg/cfg.h"
 #include "core/settings.h"
 #include "network/network.h"
 
@@ -139,7 +140,8 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
     // attempt to connect in a different thread
     QFuture<void> f = QtConcurrent::run([nickname, ip, port, password] {
         if (auto room_member = Network::GetRoomMember().lock()) {
-            room_member->Join(nickname, ip.c_str(), port, 0, Network::NoPreferredMac, password);
+            room_member->Join(nickname, Service::CFG::GetConsoleIdHash(Core::System::GetInstance()),
+                              ip.c_str(), port, 0, Network::NoPreferredMac, password);
         }
     });
     watcher->setFuture(f);
diff --git a/src/citra_qt/multiplayer/message.cpp b/src/citra_qt/multiplayer/message.cpp
index 2489a6ffd..e13463a21 100644
--- a/src/citra_qt/multiplayer/message.cpp
+++ b/src/citra_qt/multiplayer/message.cpp
@@ -38,6 +38,9 @@ const ConnectionError GENERIC_ERROR(
 const ConnectionError LOST_CONNECTION(QT_TR_NOOP("Connection to room lost. Try to reconnect."));
 const ConnectionError MAC_COLLISION(
     QT_TR_NOOP("MAC address is already in use. Please choose another."));
+const ConnectionError CONSOLE_ID_COLLISION(QT_TR_NOOP(
+    "Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation "
+    "> Configure > System to regenerate your Console ID."));
 
 static bool WarnMessage(const std::string& title, const std::string& text) {
     return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
diff --git a/src/citra_qt/multiplayer/message.h b/src/citra_qt/multiplayer/message.h
index 46461c3c1..cc8e0f4a4 100644
--- a/src/citra_qt/multiplayer/message.h
+++ b/src/citra_qt/multiplayer/message.h
@@ -37,6 +37,7 @@ extern const ConnectionError WRONG_PASSWORD;
 extern const ConnectionError GENERIC_ERROR;
 extern const ConnectionError LOST_CONNECTION;
 extern const ConnectionError MAC_COLLISION;
+extern const ConnectionError CONSOLE_ID_COLLISION;
 
 /**
  *  Shows a standard QMessageBox with a error message
diff --git a/src/citra_qt/multiplayer/state.cpp b/src/citra_qt/multiplayer/state.cpp
index d5c15dcb6..d819a3d0f 100644
--- a/src/citra_qt/multiplayer/state.cpp
+++ b/src/citra_qt/multiplayer/state.cpp
@@ -102,6 +102,9 @@ void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& s
     case Network::RoomMember::State::MacCollision:
         NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION);
         break;
+    case Network::RoomMember::State::ConsoleIdCollision:
+        NetworkMessage::ShowError(NetworkMessage::CONSOLE_ID_COLLISION);
+        break;
     case Network::RoomMember::State::RoomIsFull:
         NetworkMessage::ShowError(NetworkMessage::ROOM_IS_FULL);
         break;
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index d5c08a5a7..8c6859bb3 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -734,4 +734,21 @@ void InstallInterfaces(Core::System& system) {
     std::make_shared<CFG_NOR>()->InstallAsService(service_manager);
 }
 
+std::string GetConsoleIdHash(Core::System& system) {
+    u64_le console_id{};
+    std::array<u8, sizeof(console_id)> buffer;
+    if (system.IsPoweredOn()) {
+        auto cfg = GetModule(system);
+        ASSERT_MSG(cfg, "CFG Module missing!");
+        console_id = cfg->GetConsoleUniqueId();
+    } else {
+        console_id = std::make_unique<Service::CFG::Module>()->GetConsoleUniqueId();
+    }
+    std::memcpy(buffer.data(), &console_id, sizeof(console_id));
+
+    std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash;
+    CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer));
+    return fmt::format("{:02x}", fmt::join(hash.begin(), hash.end(), ""));
+}
+
 } // namespace Service::CFG
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h
index b069ad728..af63c6f91 100644
--- a/src/core/hle/service/cfg/cfg.h
+++ b/src/core/hle/service/cfg/cfg.h
@@ -415,4 +415,7 @@ std::shared_ptr<Module> GetModule(Core::System& system);
 
 void InstallInterfaces(Core::System& system);
 
+/// Convenience function for getting a SHA256 hash of the Console ID
+std::string GetConsoleIdHash(Core::System& system);
+
 } // namespace Service::CFG
diff --git a/src/network/room.cpp b/src/network/room.cpp
index a5c39d2b6..9ae26f524 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -31,10 +31,11 @@ public:
     std::string password; ///< The password required to connect to this room.
 
     struct Member {
-        std::string nickname;   ///< The nickname of the member.
-        GameInfo game_info;     ///< The current game of the member
-        MacAddress mac_address; ///< The assigned mac address of the member.
-        ENetPeer* peer;         ///< The remote peer.
+        std::string nickname;        ///< The nickname of the member.
+        std::string console_id_hash; ///< A hash of the console ID of the member.
+        GameInfo game_info;          ///< The current game of the member
+        MacAddress mac_address;      ///< The assigned mac address of the member.
+        ENetPeer* peer;              ///< The remote peer.
     };
     using MemberList = std::vector<Member>;
     MemberList members;              ///< Information about the members of this room
@@ -69,6 +70,12 @@ public:
      */
     bool IsValidMacAddress(const MacAddress& address) const;
 
+    /**
+     * Returns whether the console ID (hash) is valid, ie. isn't already taken by someone else in
+     * the room.
+     */
+    bool IsValidConsoleId(const std::string& console_id_hash) const;
+
     /**
      * Sends a ID_ROOM_IS_FULL message telling the client that the room is full.
      */
@@ -84,6 +91,12 @@ public:
      */
     void SendMacCollision(ENetPeer* client);
 
+    /**
+     * Sends a IdConsoleIdCollison message telling the client that another member with the same
+     * console ID exists.
+     */
+    void SendConsoleIdCollision(ENetPeer* client);
+
     /**
      * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
      */
@@ -212,6 +225,9 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
     std::string nickname;
     packet >> nickname;
 
+    std::string console_id_hash;
+    packet >> console_id_hash;
+
     MacAddress preferred_mac;
     packet >> preferred_mac;
 
@@ -242,6 +258,11 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
         preferred_mac = GenerateMacAddress();
     }
 
+    if (!IsValidConsoleId(console_id_hash)) {
+        SendConsoleIdCollision(event->peer);
+        return;
+    }
+
     if (client_version != network_version) {
         SendVersionMismatch(event->peer);
         return;
@@ -250,6 +271,7 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
     // At this point the client is ready to be added to the room.
     Member member{};
     member.mac_address = preferred_mac;
+    member.console_id_hash = console_id_hash;
     member.nickname = nickname;
     member.peer = event->peer;
 
@@ -282,6 +304,14 @@ bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {
                        [&address](const auto& member) { return member.mac_address != address; });
 }
 
+bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const {
+    // A Console ID is valid if it is not already taken by anybody else in the room.
+    std::lock_guard<std::mutex> lock(member_mutex);
+    return std::all_of(members.begin(), members.end(), [&console_id_hash](const auto& member) {
+        return member.console_id_hash != console_id_hash;
+    });
+}
+
 void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
     Packet packet;
     packet << static_cast<u8>(IdNameCollision);
@@ -302,6 +332,16 @@ void Room::RoomImpl::SendMacCollision(ENetPeer* client) {
     enet_host_flush(server);
 }
 
+void Room::RoomImpl::SendConsoleIdCollision(ENetPeer* client) {
+    Packet packet;
+    packet << static_cast<u8>(IdConsoleIdCollision);
+
+    ENetPacket* enet_packet =
+        enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+    enet_peer_send(client, 0, enet_packet);
+    enet_host_flush(server);
+}
+
 void Room::RoomImpl::SendWrongPassword(ENetPeer* client) {
     Packet packet;
     packet << static_cast<u8>(IdWrongPassword);
diff --git a/src/network/room.h b/src/network/room.h
index 19bb5acce..7a73022bc 100644
--- a/src/network/room.h
+++ b/src/network/room.h
@@ -59,6 +59,7 @@ enum RoomMessageTypes : u8 {
     IdWrongPassword,
     IdCloseRoom,
     IdRoomIsFull,
+    IdConsoleIdCollision,
 };
 
 /// This is what a server [person creating a server] would use.
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index 971c05c18..682821aa3 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -73,11 +73,12 @@ public:
      * Sends a request to the server, asking for permission to join a room with the specified
      * nickname and preferred mac.
      * @params nickname The desired nickname.
+     * @params console_id_hash A hash of the Console ID.
      * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
      * @params password The password for the room
      * the server to assign one for us.
      */
-    void SendJoinRequest(const std::string& nickname,
+    void SendJoinRequest(const std::string& nickname, const std::string& console_id_hash,
                          const MacAddress& preferred_mac = NoPreferredMac,
                          const std::string& password = "");
 
@@ -163,6 +164,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
                 case IdMacCollision:
                     SetState(State::MacCollision);
                     break;
+                case IdConsoleIdCollision:
+                    SetState(State::ConsoleIdCollision);
+                    break;
                 case IdVersionMismatch:
                     SetState(State::WrongVersion);
                     break;
@@ -204,11 +208,13 @@ void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
 }
 
 void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname,
+                                                 const std::string& console_id_hash,
                                                  const MacAddress& preferred_mac,
                                                  const std::string& password) {
     Packet packet;
     packet << static_cast<u8>(IdJoinRequest);
     packet << nickname;
+    packet << console_id_hash;
     packet << preferred_mac;
     packet << network_version;
     packet << password;
@@ -392,9 +398,9 @@ RoomInformation RoomMember::GetRoomInformation() const {
     return room_member_impl->room_information;
 }
 
-void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
-                      u16 client_port, const MacAddress& preferred_mac,
-                      const std::string& password) {
+void RoomMember::Join(const std::string& nick, const std::string& console_id_hash,
+                      const char* server_addr, u16 server_port, u16 client_port,
+                      const MacAddress& preferred_mac, const std::string& password) {
     // If the member is connected, kill the connection first
     if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
         Leave();
@@ -427,7 +433,7 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv
     if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
         room_member_impl->nickname = nick;
         room_member_impl->StartLoop();
-        room_member_impl->SendJoinRequest(nick, preferred_mac, password);
+        room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password);
         SendGameInfo(room_member_impl->current_game_info);
     } else {
         enet_peer_disconnect(room_member_impl->server, 0);
diff --git a/src/network/room_member.h b/src/network/room_member.h
index b8a648f1d..58fca96b7 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -54,12 +54,13 @@ public:
         LostConnection, ///< Connection closed
 
         // Reasons why connection was rejected
-        NameCollision,   ///< Somebody is already using this name
-        MacCollision,    ///< Somebody is already using that mac-address
-        WrongVersion,    ///< The room version is not the same as for this RoomMember
-        WrongPassword,   ///< The password doesn't match the one from the Room
-        CouldNotConnect, ///< The room is not responding to a connection attempt
-        RoomIsFull       ///< Room is already at the maximum number of players
+        NameCollision,      ///< Somebody is already using this name
+        MacCollision,       ///< Somebody is already using that mac-address
+        ConsoleIdCollision, ///< Somebody in the room has the same Console ID
+        WrongVersion,       ///< The room version is not the same as for this RoomMember
+        WrongPassword,      ///< The password doesn't match the one from the Room
+        CouldNotConnect,    ///< The room is not responding to a connection attempt
+        RoomIsFull          ///< Room is already at the maximum number of players
     };
 
     struct MemberInformation {
@@ -116,11 +117,13 @@ public:
 
     /**
      * Attempts to join a room at the specified address and port, using the specified nickname.
-     * This may fail if the username is already taken.
+     * A console ID hash is passed in to check console ID conflicts.
+     * This may fail if the username or console ID is already taken.
      */
-    void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
-              const u16 server_port = DefaultRoomPort, const u16 client_port = 0,
-              const MacAddress& preferred_mac = NoPreferredMac, const std::string& password = "");
+    void Join(const std::string& nickname, const std::string& console_id_hash,
+              const char* server_addr = "127.0.0.1", const u16 server_port = DefaultRoomPort,
+              const u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac,
+              const std::string& password = "");
 
     /**
      * Sends a WiFi packet to the room.