Mutual authentication: client side
authorunc0rr
Sun, 26 Jan 2014 00:08:49 +0400
changeset 10074 66cab76eb56f
parent 10073 865a4089278d
child 10075 dbaf90a0fbe0
Mutual authentication: client side
QTfrontend/hwconsts.cpp.in
QTfrontend/net/newnetclient.cpp
QTfrontend/net/newnetclient.h
--- a/QTfrontend/hwconsts.cpp.in	Fri Jan 24 22:38:15 2014 +0400
+++ b/QTfrontend/hwconsts.cpp.in	Sun Jan 26 00:08:49 2014 +0400
@@ -37,7 +37,7 @@
 bool custom_data = false;
 
 int cMaxTeams = 8;
-int cMinServerVersion = 1;
+int cMinServerVersion = 3;
 
 QString * cDefaultAmmoStore = new QString( AMMOLINE_DEFAULT_QT AMMOLINE_DEFAULT_PROB
                                            AMMOLINE_DEFAULT_DELAY AMMOLINE_DEFAULT_CRATE );
--- a/QTfrontend/net/newnetclient.cpp	Fri Jan 24 22:38:15 2014 +0400
+++ b/QTfrontend/net/newnetclient.cpp	Sun Jan 26 00:08:49 2014 +0400
@@ -21,6 +21,7 @@
 #include <QInputDialog>
 #include <QCryptographicHash>
 #include <QSortFilterProxyModel>
+#include <QUuid>
 
 #include "hwconsts.h"
 #include "newnetclient.h"
@@ -36,7 +37,6 @@
 HWNewNet::HWNewNet() :
     isChief(false),
     m_game_connected(false),
-    loginStep(0),
     netClientState(Disconnected)
 {
     m_roomsListModel = new RoomsListModel(this);
@@ -238,7 +238,10 @@
 
 void HWNewNet::SendPasswordHash(const QString & hash)
 {
-    RawSendNet(QString("PASSWORD%1%2").arg(delimeter).arg(hash));
+    // don't send it immediately, only store and check if server asked us for a password
+    m_passwordHash = hash;
+
+    maybeSendPassword();
 }
 
 void HWNewNet::ParseCmd(const QStringList & lst)
@@ -300,6 +303,28 @@
         return;
     }
 
+    if (lst[0] == "SERVER_AUTH")
+    {
+        if(lst.size() < 2)
+        {
+            qWarning("Net: Malformed SERVER_AUTH message");
+            return;
+        }
+
+        if(lst[2] != m_serverHash)
+        {
+            Error("Server authentication error");
+            Disconnect();
+        } else
+        {
+            // empty m_serverHash variable means no authentication was performed
+            // or server passed authentication
+            m_serverHash.clear();
+        }
+
+        return;
+    }
+
     if (lst[0] == "PING")
     {
         if (lst.size() > 1)
@@ -515,6 +540,14 @@
         {
             if (lst[i] == mynick)
             {
+                // check if server is authenticated or no authentication was performed at all
+                if(!m_serverHash.isEmpty())
+                {
+                    Error(tr("Server authentication error"));
+
+                    Disconnect();
+                }
+
                 netClientState = InLobby;
                 RawSendNet(QString("LIST"));
                 emit connected();
@@ -578,8 +611,24 @@
 
     if (lst[0] == "ASKPASSWORD")
     {
+        // server should send us salt of at least 16 characters
+
+        if(lst.size() < 2 || lst[1].size() < 16)
+        {
+            qWarning("Net: Bad ASKPASSWORD message");
+            return;
+        }
+
         emit NickRegistered(mynick);
         m_nick_registered = true;
+
+        // store server salt
+        // when this variable is set, it is assumed that server asked us for a password
+        m_serverSalt = lst[1];
+        m_clientSalt = QUuid::createUuid().toString();
+
+        maybeSendPassword();
+
         return;
     }
 
@@ -1083,3 +1132,36 @@
     if(!myroom.isEmpty())
         JoinRoom(myroom, password);
 }
+
+void HWNewNet::maybeSendPassword()
+{
+/* When we got password hash, and server asked us for a password, perform mutual authentication:
+ * at this point we have salt chosen by server
+ * client sends client salt and hash of secret (password hash) salted with client salt, server salt,
+ * and static salt (predefined string + protocol number)
+ * server should respond with hash of the same set in different order.
+ */
+
+    if(m_passwordHash.isEmpty() || m_serverSalt.isEmpty())
+        return;
+
+    QString hash;
+
+    hash = QCryptographicHash::hash(
+                m_clientSalt.toAscii()
+                .append(m_serverSalt.toAscii())
+                .append(m_passwordHash)
+                .append(cProtoVer->toAscii())
+                .append("!hedgewars")
+                , QCryptographicHash::Sha1);
+
+    m_serverHash = QCryptographicHash::hash(
+                m_serverSalt.toAscii()
+                .append(m_clientSalt.toAscii())
+                .append(m_passwordHash)
+                .append(cProtoVer->toAscii())
+                .append("!hedgewars")
+                , QCryptographicHash::Sha1);
+
+    RawSendNet(QString("PASSWORD%1%2%1%3").arg(delimeter).arg(hash).arg(m_clientSalt));
+}
--- a/QTfrontend/net/newnetclient.h	Fri Jan 24 22:38:15 2014 +0400
+++ b/QTfrontend/net/newnetclient.h	Sun Jan 26 00:08:49 2014 +0400
@@ -77,6 +77,10 @@
         QSortFilterProxyModel * m_lobbyPlayersModel;
         QSortFilterProxyModel * m_roomPlayersModel;
         QString m_lastRoom;
+        QString m_passwordHash;
+        QString m_serverSalt;
+        QString m_clientSalt;
+        QString m_serverHash;
 
         QStringList cmdbuf;
 
@@ -85,7 +89,8 @@
         void ParseCmd(const QStringList & lst);
         void handleNotice(int n);
 
-        int loginStep;
+        void maybeSendPassword();
+
         ClientState netClientState;
 
     signals: