From: APTX Date: Sun, 1 Jan 2012 22:58:28 +0000 (+0100) Subject: Add ENCRYPT support. May be buggy. X-Git-Url: https://gitweb.aptx.org/?a=commitdiff_plain;h=c6c3a2f07138986454265bdac78a08c0e63872f0;p=anidbudpclient.git Add ENCRYPT support. May be buggy. --- diff --git a/anidbudpclient.pro b/anidbudpclient.pro index 4995cc5..6dcdc62 100644 --- a/anidbudpclient.pro +++ b/anidbudpclient.pro @@ -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 { diff --git a/anidbudpclient_global.h b/anidbudpclient_global.h index 35b9629..aee17d6 100644 --- a/anidbudpclient_global.h +++ b/anidbudpclient_global.h @@ -32,6 +32,7 @@ namespace AniDBUdpClient ClientVersionOutdatedError, ServerError, ConnectionTimedOutError, + EncryptionError, UnknownError, }; diff --git a/client.cpp b/client.cpp index 38d213b..5626d91 100644 --- a/client.cpp +++ b/client.cpp @@ -16,6 +16,11 @@ #include +#ifndef ANIDBUDPCLIENT_NO_ENCRYPT +# include +# include +#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); } diff --git a/client.h b/client.h index 78e7781..17d9b56 100644 --- a/client.h +++ b/client.h @@ -7,6 +7,7 @@ #include #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 index 0000000..9cc3e58 --- /dev/null +++ b/encryptcommand.cpp @@ -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 index 0000000..fd8c7fe --- /dev/null +++ b/encryptcommand.h @@ -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