]> Some of my projects - anidbudpclient.git/commitdiff
Add ENCRYPT support. May be buggy.
authorAPTX <marek321@gmail.com>
Sun, 1 Jan 2012 22:58:28 +0000 (23:58 +0100)
committerAPTX <marek321@gmail.com>
Sun, 1 Jan 2012 22:58:28 +0000 (23:58 +0100)
anidbudpclient.pro
anidbudpclient_global.h
client.cpp
client.h
encryptcommand.cpp [new file with mode: 0644]
encryptcommand.h [new file with mode: 0644]

index 4995cc5ebc506c05df7e86f41998eec38f491e3c..6dcdc62bba1ceb91296136fb566705059073ec41 100644 (file)
@@ -2,7 +2,7 @@
 # Project created by QtCreator 2009-03-22T14:53:52
 # -------------------------------------------------
 QT += network \
-    script
+       script
 QT -= gui
 TEMPLATE = lib
 TARGET = anidbudpclient
@@ -10,64 +10,66 @@ TARGET = anidbudpclient
 
 include(config.pri)
 
-static { 
-    message(anidbpudpclinet: Static build)
+static {
+       message(anidbpudpclinet: Static build)
        DESTDIR = build-static
 }
-!static { 
-    message(anidbpudpclinet: Dynamic build)
+!static {
+       message(anidbpudpclinet: Dynamic build)
        DESTDIR = build
 }
 
 DEFINES += ANIDBUDPCLIENT_LIBRARY
 SOURCES += client.cpp \
-    abstractcommand.cpp \
-    authcommand.cpp \
-    rawcommand.cpp \
-    mylistaddcommand.cpp \
-    logoutcommand.cpp \
-    uptimecommand.cpp \
+       abstractcommand.cpp \
+       authcommand.cpp \
+       encryptcommand.cpp \
+       rawcommand.cpp \
+       mylistaddcommand.cpp \
+       logoutcommand.cpp \
+       uptimecommand.cpp \
        mylistcommand.cpp \
        filecommand.cpp \
        votecommand.cpp \
        file.cpp \
-    hash.cpp \
-    hashproducer.cpp \
+       hash.cpp \
+       hashproducer.cpp \
        hashconsumer.cpp \
-    clientsentcommandsmodel.cpp \
+       clientsentcommandsmodel.cpp \
        clientqueuedcommandsmodel.cpp \
        filerenamedelegate.cpp \
        clientinterface.cpp \
-    myliststate.cpp
+       myliststate.cpp
 
 HEADERS += client.h \
-    anidbudpclient_global.h \
+       anidbudpclient_global.h \
        aniqflags.h \
-    abstractcommand.h \
-    authcommand.h \
-    rawcommand.h \
-    mylistaddcommand.h \
-    logoutcommand.h \
-    uptimecommand.h \
+       abstractcommand.h \
+       authcommand.h \
+       encryptcommand.h \
+       rawcommand.h \
+       mylistaddcommand.h \
+       logoutcommand.h \
+       uptimecommand.h \
        mylistcommand.h \
        filecommand.h \
        votecommand.h \
        file.h \
-    hash.h \
-    hashproducer.h \
-    hashconsumer.h \
+       hash.h \
+       hashproducer.h \
+       hashconsumer.h \
        circularbuffer.h \
-    clientsentcommandsmodel.h \
+       clientsentcommandsmodel.h \
        clientqueuedcommandsmodel.h \
        filerenamedelegate.h \
        clientinterface.h \
        myliststate.h
 
 CONV_HEADERS += include/AniDBUdpClient/Client \
-    include/AniDBUdpClient/AbstractCommand \
-    include/AniDBUdpClient/RawCommand \
-    include/AniDBUdpClient/MyListCommand \
-    include/AniDBUdpClient/MyListAddCommand \
+       include/AniDBUdpClient/AbstractCommand \
+       include/AniDBUdpClient/RawCommand \
+       include/AniDBUdpClient/MyListCommand \
+       include/AniDBUdpClient/MyListAddCommand \
        include/AniDBUdpClient/MyListState \
        include/AniDBUdpClient/FileCommand \
        include/AniDBUdpClient/VoteCommand \
@@ -99,6 +101,14 @@ noproxy {
        message(Disabled proxy support)
 }
 
+!noencrypt {
+       CONFIG += crypto
+}
+noencrypt {
+       DEFINES += ANIDBUDPCLIENT_NO_ENCRYPT
+       message(Disabled ENCRYPT support)
+}
+
 # RenameParser Files
 
 !norenameparser {
index 35b962941d1cb003d467ce8ff960b549fb5f03bc..aee17d6ac4ae2c882eefbd19f3f6c823bc0810d0 100644 (file)
@@ -32,6 +32,7 @@ namespace AniDBUdpClient
                ClientVersionOutdatedError,
                ServerError,
                ConnectionTimedOutError,
+               EncryptionError,
                UnknownError,
        };
 
index 38d213b7355f1577a2905ba6bfc3a813a61a3ad7..5626d910dce0ee15baefe962a736a6d5ee0504be 100644 (file)
 
 #include <QtDebug>
 
+#ifndef ANIDBUDPCLIENT_NO_ENCRYPT
+#  include <QCryptographicHash>
+#  include <QtCrypto>
+#endif
+
 namespace AniDBUdpClient {
 
 const QByteArray Client::clientName = CLIENT_NAME;
@@ -29,11 +34,14 @@ qDebug() << "Api instance init!";
 #endif
        authReply = 0;
        uptimeReply = 0;
+       encryptReply = 0;
 
        m_idlePolicy = DoNothingIdlePolicy;
+       m_enableEncryption = false;
 
        disconnecting = false;
        authenticatingStarted = false;
+       usingEncryption = false;
        commandsTimedOut = 0;
 
        socket = new QUdpSocket(this);
@@ -57,6 +65,7 @@ qDebug() << "Api instance init!";
        connectingState = new QState;
        connectedState = new QState;
        authenticatingState = new QState(connectedState);
+       encryptionState = new QState(connectedState);
        idleState = new QState(connectedState);
        idleTimeoutState = new QState(connectedState);
        sendState = new QState(connectedState);
@@ -88,9 +97,13 @@ qDebug() << "Api instance init!";
        authenticatingState->addTransition(this, SIGNAL(startSending()), sendState);
        authenticatingState->addTransition(this, SIGNAL(authenticated()), sendState);
 
+       encryptionState->addTransition(this, SIGNAL(startSending()), sendState);
+       encryptionState->addTransition(this, SIGNAL(encryptionEstablished()), sendState);
+
        sendState->addTransition(this, SIGNAL(queueEmpty()), idleState);
        sendState->addTransition(this, SIGNAL(commandSent()), waitState);
        sendState->addTransition(this, SIGNAL(startAuthentication()), authenticatingState);
+       sendState->addTransition(this, SIGNAL(startEncryption()), encryptionState);
 
        waitState->addTransition(commandTimer, SIGNAL(timeout()), sendState);
 
@@ -115,6 +128,7 @@ qDebug() << "Api instance init!";
        QObject::connect(connectingState, SIGNAL(entered()), this, SLOT(enterConnectingState()));
        QObject::connect(connectedState, SIGNAL(entered()), this, SLOT(enterConnectedState()));
        QObject::connect(authenticatingState, SIGNAL(entered()), this, SLOT(enterAuthenticatingState()));
+       QObject::connect(encryptionState, SIGNAL(entered()), this, SLOT(enterEncryptionState()));
        QObject::connect(sendState, SIGNAL(entered()), this, SLOT(enterSendState()));
        QObject::connect(waitState, SIGNAL(entered()), this, SLOT(enterWaitState()));
        QObject::connect(idleState, SIGNAL(entered()), this, SLOT(enterIdleState()));
@@ -171,6 +185,7 @@ QString Client::user() const
 void Client::setUser(const QString &user)
 {
        authCommand.setUser(user);
+       encryptCommand.setUser(user);
 }
 
 QString Client::pass() const
@@ -183,6 +198,26 @@ void Client::setPass(const QString &pass)
        authCommand.setPass(pass);
 }
 
+QString Client::apiKey() const
+{
+       return m_apiKey;
+}
+
+void Client::setApiKey(const QString &apiKey)
+{
+       m_apiKey = apiKey;
+}
+
+bool Client::encryptionEnabled() const
+{
+       return m_enableEncryption;
+}
+
+void Client::setEncryptionEnabled(bool enabled)
+{
+       m_enableEncryption = enabled;
+}
+
 bool Client::compression() const
 {
        return authCommand.compression();
@@ -329,6 +364,51 @@ qDebug() << "success!";
        emit connectionError();
 }
 
+void Client::enterEncryptionState()
+{
+#ifdef ANIDBUDPCLIENT_CLIENT_STATE_MACHINE_DEBUG
+qDebug() << "Entering Encryption State";
+#endif
+       if (encryptionStarted)
+               return;
+
+       encryptionStarted = true;
+
+       if (m_enableEncryption && !usingEncryption)
+       {
+               if (encryptReply != 0) encryptReply->deleteLater();
+               encryptReply = createReply(encryptCommand);
+               QObject::connect(encryptReply, SIGNAL(replyReady(bool)), this, SLOT(doEncrypt(bool)));
+               enqueueControlCommand(encryptReply, true);
+               return;
+       }
+       encryptionStarted = false;
+       emit encryptionEstablished();
+}
+
+void Client::doEncrypt(bool success)
+{
+#ifdef ANIDBUDPCLIENT_CLIENT_STATE_MACHINE_DEBUG
+qDebug() << "doEncrypt init";
+#endif
+       authenticatingStarted = false;
+       if (success)
+       {
+#ifdef ANIDBUDPCLIENT_CLIENT_STATE_MACHINE_DEBUG
+qDebug() << "success!";
+#endif
+               m_salt = encryptReply->salt();
+               usingEncryption = true;
+               emit encryptionEstablished();
+               return;
+       }
+
+       m_error = EncryptionError;
+       m_errorString = encryptReply->errorString();
+
+       emit connectionError();
+}
+
 void Client::enterSendState()
 {
 #ifdef ANIDBUDPCLIENT_CLIENT_STATE_MACHINE_DEBUG
@@ -427,6 +507,8 @@ qDebug() << "Entering IdleTiemout State";
        {
                case DoNothingIdlePolicy:
                        m_sessionId = "";
+                       m_salt = "";
+                       usingEncryption = false;
                break;
                case KeepAliveIdlePolicy:
                        enqueueControlCommand(uptimeReply);
@@ -459,6 +541,14 @@ qDebug() << "Entering Recieve State";
                        continue;
                }
 
+               if (usingEncryption)
+               {
+#ifdef ANIDBUDPCLIENT_CLIENT_MISC_DEBUG
+qDebug() << "ENCRYPED DATAGRAM = " << data;
+#endif
+                       tmp = decrypt(tmp);
+               }
+
                if (authCommand.compression() && tmp.mid(0, 2) == "00")
                {
 #ifdef ANIDBUDPCLIENT_CLIENT_MISC_DEBUG
@@ -481,7 +571,6 @@ qDebug() << QString("Recieved datagram from [%1]:%2\nRaw datagram contents:%3")
                        QRegExp rx("(?:50[34]|555|6[0-9]{2}) ");
                        if (rx.exactMatch(tmp.mid(0, 4)))
                        {
-                       
                                int replyCode = tmp.mid(0, 3).toInt();
                                switch (replyCode)
                                {
@@ -599,6 +688,8 @@ qDebug() << "LOGIN FIRST required, authing";
                        break;
                        case LOGGED_OUT:
                                m_sessionId.clear();
+                               m_salt.clear();
+                               usingEncryption = false;
                        break;
                        case CLIENT_VERSION_OUTDATED:
                                m_error = ClientVersionOutdatedError;
@@ -783,6 +874,88 @@ void Client::logout(bool force)
        enqueueControlCommand(createReply(LogoutCommand()), force);
 }
 
+#ifndef ANIDBUDPCLIENT_NO_ENCRYPT
+QByteArray Client::encrypt(const QByteArray &data)
+{
+       QCA::init();
+       if (!QCA::isSupported("aes128-ecb"))
+       {
+               qDebug() << "encryption failed due to no aes128-cbc";
+               return QByteArray();
+       }
+
+       QCA::SymmetricKey key(QCryptographicHash::hash((m_apiKey + m_salt).toUtf8(), QCryptographicHash::Md5));
+
+       // PKCS5 (or 7) padding
+       char pad = 16 - char(data.size() % 16);
+       QByteArray paddedData = data;
+       paddedData.reserve(data.size() + pad);
+       for (int i = 0; i < pad; ++i)
+               paddedData += pad;
+
+#ifdef ANIDBUDPCLIENT_CLIENT_ENCRYPT_DEBUG
+qDebug() << "PADDED DATA\n\t" << paddedData << "\n\t" << paddedData.toHex();
+#endif
+
+       QByteArray ret;
+       QCA::Cipher c("aes128", QCA::Cipher::ECB, QCA::Cipher::NoPadding, QCA::Encode, key);
+       ret = c.update(paddedData).toByteArray();
+       c.final(); // Should be empty as data is already padded
+//     QCA::deinit();
+       return ret;
+}
+
+QByteArray Client::decrypt(const QByteArray &data)
+{
+       QCA::init();
+       if (!QCA::isSupported("aes128-ecb"))
+       {
+               qDebug() << "encryption failed due to no aes128-cbc";
+               return QByteArray();
+       }
+
+       QCA::SymmetricKey key(QCryptographicHash::hash((m_apiKey + m_salt).toUtf8(), QCryptographicHash::Md5));
+
+       QByteArray ret;
+#ifdef ANIDBUDPCLIENT_CLIENT_ENCRYPT_DEBUG
+qDebug() << " -- DECRYPT --";
+#endif
+       QCA::Cipher c("aes128", QCA::Cipher::ECB, QCA::Cipher::NoPadding, QCA::Decode, key);
+       ret = c.update(data).toByteArray();
+#ifdef ANIDBUDPCLIENT_CLIENT_ENCRYPT_DEBUG
+qDebug() << "PART 1 = " << ret;
+#endif
+       QByteArray p2;
+       p2 = c.final().toByteArray();
+#ifdef ANIDBUDPCLIENT_CLIENT_ENCRYPT_DEBUG
+qDebug() << "PART 2 = " << p2;
+#endif
+       ret += p2;
+
+       // Remove padding (and only it)
+       char pad = ret[ret.size() - 1];
+       if (pad > 0 && pad < 16)
+       {
+               QByteArray padding = ret.right(pad);
+               bool ok = true;
+               for (int i = 0; i < padding.size(); ++i)
+               {
+                       if (padding[i] != pad)
+                       {
+                               ok = false;
+                               break;
+                       }
+               }
+               if (ok)
+               {
+                       ret = ret.left(ret.size() - pad);
+               }
+       }
+//     QCA::deinit();
+       return ret;
+}
+#endif
+
 void Client::commandTimeout()
 {
        Q_ASSERT(!sentCommandOrder.isEmpty());
@@ -851,6 +1024,16 @@ void Client::enqueueControlCommand(AbstractReply *command, bool first)
 
 void Client::sendCommand(AbstractReply *command, bool controlCommand)
 {
+       if (m_enableEncryption && command->command().requiresSession() && (m_salt.isEmpty() || !usingEncryption))
+       {
+               if (controlCommand)
+                       enqueueControlCommand(command, true);
+               else
+                       enqueueCommand(command, true);
+               emit startEncryption();
+               return;
+       }
+
        if (m_sessionId.isEmpty() && command->command().requiresSession())
        {
                if (controlCommand)
@@ -872,7 +1055,6 @@ void Client::sendCommand(AbstractReply *command, bool controlCommand)
        if (m_sessionId.length())
                datagram += "&s=" + m_sessionId;
 
-
        command->setControlCommand(controlCommand);
        command->setTimeSent();
 
@@ -889,13 +1071,24 @@ qDebug() << "Starting replyTimeoutTimer" << replyTimeoutTimer->interval();
        }
 
 #ifdef ANIDBUDPCLIENT_CLIENT_MISC_DEBUG
-qDebug() << QString("SENDING datagram:\n\t%1\nto: %2 ([%3]:%4)")
+qDebug() << QString("SENDING datagram:\n\t%1\nto: %2 ([%3]:%4) (%5 encryption)")
                .arg(datagram.constData())
                .arg(m_host)
                .arg(m_hostAddress.toString())
-               .arg(m_hostPort);
+               .arg(m_hostPort)
+               .arg(usingEncryption ? "with" : "without");
 #endif
 
+       if (usingEncryption)
+       {
+               datagram = encrypt(datagram);
+
+#ifdef ANIDBUDPCLIENT_CLIENT_MISC_DEBUG
+qDebug() << QString("ENCRYPTED datagram:\n\t%1")
+               .arg(datagram.constData());
+#endif
+       }
+
        socket->writeDatagram(datagram, m_hostAddress, m_hostPort);
 }
 
index 78e77812a763b8bda022c42b762aef5002975cf2..17d9b560d61b97a8fb58047bc136ee592fbf464c 100644 (file)
--- a/client.h
+++ b/client.h
@@ -7,6 +7,7 @@
 #include <QTimer>
 
 #include "authcommand.h"
+#include "encryptcommand.h"
 
 class QStateMachine;
 class QState;
@@ -52,6 +53,11 @@ public:
        void setUser(const QString &user);
        QString pass() const;
        void setPass(const QString &user);
+       QString apiKey() const;
+       void setApiKey(const QString &apiKey);
+
+       bool encryptionEnabled() const;
+       void setEncryptionEnabled(bool enabled = true);
 
        bool compression() const;
        void setCompression(bool compress);
@@ -105,6 +111,10 @@ signals:
        void authenticated();
        void authenticationFailure();
 
+       void startEncryption();
+       void encryptionEstablished();
+       void encryptionFailure();
+
        void startSending();
        void commandSent();
        void queueEmpty();
@@ -129,6 +139,9 @@ private slots:
        void enterAuthenticatingState();
        void doAuthenticate(bool success);
 
+       void enterEncryptionState();
+       void doEncrypt(bool success);
+
        void enterSendState();
        void enterWaitState();
        void enterIdleState();
@@ -151,6 +164,11 @@ private:
 
        void logout(bool force);
 
+#ifndef ANIDBUDPCLIENT_NO_ENCRYPT
+       QByteArray encrypt(const QByteArray &data);
+       QByteArray decrypt(const QByteArray &data);
+#endif
+
        QTimer *commandTimer;
        QTimer *idleTimer;
        QTimer *replyTimeoutTimer;
@@ -165,6 +183,9 @@ private:
        int m_floodInterval;
 
        QByteArray m_sessionId;
+       QString m_salt;
+       bool m_enableEncryption;
+       QString m_apiKey;
 
        // Misc params
        IdlePolicy m_idlePolicy;
@@ -172,12 +193,16 @@ private:
 
        bool disconnecting;
        bool authenticatingStarted;
+       bool encryptionStarted;
+       bool usingEncryption;
 
        int commandsTimedOut;
 
        AuthCommand authCommand;
        AuthReply *authReply;
        UptimeReply *uptimeReply;
+       EncryptCommand encryptCommand;
+       EncryptReply *encryptReply;
 
        static Client *m_instance;
 
@@ -195,6 +220,7 @@ private:
        QState *connectingState;
        QState *connectedState;
        QState *authenticatingState;
+       QState *encryptionState;
 
        QState *idleState;
        QState *idleTimeoutState;
diff --git a/encryptcommand.cpp b/encryptcommand.cpp
new file mode 100644 (file)
index 0000000..9cc3e58
--- /dev/null
@@ -0,0 +1,101 @@
+#include "encryptcommand.h"
+
+namespace AniDBUdpClient {
+
+EncryptCommand::EncryptCommand(const QString &user, EncryptionType encryptionType) : AbstractCommand(), m_user(user), m_encryptionType(encryptionType)
+{
+}
+
+QString EncryptCommand::user() const
+{
+       return m_user;
+}
+
+void EncryptCommand::setUser(const QString &user)
+{
+       m_user = user;
+}
+
+EncryptCommand::EncryptionType EncryptCommand::encryptionType() const
+{
+       return m_encryptionType;
+}
+
+void EncryptCommand::setEncryptionType(EncryptCommand::EncryptionType encryptionType)
+{
+       m_encryptionType = encryptionType;
+}
+
+Command EncryptCommand::rawCommand() const
+{
+       Command cmd;
+       cmd.first = "ENCRYPT";
+       cmd.second["user"] = m_user;
+       cmd.second["type"] = int(m_encryptionType);
+       return cmd;
+}
+
+bool EncryptCommand::waitForResult() const
+{
+       return true;
+}
+
+bool EncryptCommand::requiresSession() const
+{
+       return false;
+}
+
+// ===
+
+QString EncryptReply::salt()
+{
+       return m_salt;
+}
+
+QString EncryptReply::errorString()
+{
+       return m_errorString;
+}
+
+void EncryptReply::setRawReply(ReplyCode replyCode, const QString &reply)
+{
+       AbstractReply::setRawReply(replyCode, reply);
+
+       switch (replyCode)
+       {
+               case ENCRYPTION_ENABLED:
+               {
+                       int saltEnd = reply.indexOf(' ');
+                       if (saltEnd == -1)
+                       {
+                               signalReplyReady(false);
+                               return;
+                       }
+                       m_salt = reply.left(reply.indexOf(' '));
+                       if (m_salt.isEmpty())
+                       {
+                               signalReplyReady(false);
+                               return;
+                       }
+                       signalReplyReady(true);
+               }
+               break;
+               case API_PASSWORD_NOT_DEFINED:
+                       m_errorString = tr("API password not defined.");
+                       signalReplyReady(false);
+               break;
+               case NO_SUCH_ENCRYPTION_TYPE:
+                       m_errorString = tr("No such encryption type.");
+                       signalReplyReady(false);
+               break;
+               case NO_SUCH_USER:
+                       m_errorString = tr("No such user");
+                       signalReplyReady(false);
+               break;
+               default:
+                       signalReplyReady(false);
+               break;
+       }
+}
+
+} // namespace AniDBUdpClient
diff --git a/encryptcommand.h b/encryptcommand.h
new file mode 100644 (file)
index 0000000..fd8c7fe
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef ENCRYPTCOMMAND_H
+#define ENCRYPTCOMMAND_H
+
+#include "anidbudpclient_global.h"
+#include "abstractcommand.h"
+
+namespace AniDBUdpClient {
+
+class EncryptReply;
+
+class ANIDBUDPCLIENTSHARED_EXPORT EncryptCommand : public AbstractCommand
+{
+public:
+       typedef EncryptReply ReplyType;
+
+       enum EncryptionType {
+               AES128 = 1
+       };
+
+       EncryptCommand(const QString &user = "", EncryptionType encryptionType = AES128);
+
+       QString user() const;
+       void setUser(const QString &user);
+
+       EncryptionType encryptionType() const;
+       void setEncryptionType(EncryptionType encryptionType);
+
+       Command rawCommand() const;
+       bool waitForResult() const;
+       bool requiresSession() const;
+
+private:
+       QString m_user;
+       EncryptionType m_encryptionType;
+};
+
+class ANIDBUDPCLIENTSHARED_EXPORT EncryptReply : public AbstractReply
+{
+       Q_OBJECT
+       REPLY_DEFINITION_HELPER(Encrypt)
+
+       Q_PROPERTY(QString salt READ salt)
+       Q_PROPERTY(QString errorString READ errorString)
+public:
+       QString salt();
+       QString errorString();
+
+       void setRawReply(ReplyCode replyCode, const QString &reply);
+
+private:
+       QString m_salt;
+       QString m_errorString;
+};
+
+} // namespace AniDBUdpClient
+
+#endif // ENCRYPTCOMMAND_H