Merge pull request #5 from Tucos/feat-keyx
authorManuel Nickschas <sputnick@quassel-irc.org>
Tue, 19 Feb 2013 22:27:25 +0000 (14:27 -0800)
committerManuel Nickschas <sputnick@quassel-irc.org>
Tue, 19 Feb 2013 22:27:25 +0000 (14:27 -0800)
Add support for key exchange

13 files changed:
src/common/CMakeLists.txt
src/common/eventmanager.h
src/common/keyevent.cpp [new file with mode: 0644]
src/common/keyevent.h [new file with mode: 0644]
src/core/cipher.cpp
src/core/coreircchannel.cpp
src/core/corenetwork.cpp
src/core/corenetwork.h
src/core/coresessioneventprocessor.cpp
src/core/coresessioneventprocessor.h
src/core/coreuserinputhandler.cpp
src/core/coreuserinputhandler.h
src/core/ircparser.cpp

index fe1d4f9..c247eb3 100644 (file)
@@ -78,6 +78,11 @@ set(HEADERS ${MOC_HDRS}
     types.h
     util.h)
 
+if (HAVE_QCA2)
+    set(SOURCES ${SOURCES} keyevent.cpp)
+    set(HEADERS ${HEADERS} keyevent.h)
+endif(HAVE_QCA2)
+
 if(APPLE)
   set(SOURCES ${SOURCES} mac_utils.cpp)
   set(HEADERS ${HEADERS} mac_utils.h)
index 3dbd726..edebe61 100644 (file)
@@ -113,6 +113,10 @@ public :
 
         CtcpEvent                   = 0x00050000,
         CtcpEventFlush
+
+#ifdef HAVE_QCA2
+        ,KeyEvent                    = 0x00060000
+#endif
     };
 
     EventManager(QObject *parent = 0);
diff --git a/src/common/keyevent.cpp b/src/common/keyevent.cpp
new file mode 100644 (file)
index 0000000..afeca21
--- /dev/null
@@ -0,0 +1,47 @@
+/***************************************************************************
+ *   Copyright (C) 2013 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#include "keyevent.h"
+
+Event *KeyEvent::create(EventManager::EventType type, QVariantMap &map, Network *network)
+{
+    if (type == EventManager::KeyEvent)
+        return new KeyEvent(type, map, network);
+
+    return 0;
+}
+
+
+KeyEvent::KeyEvent(EventManager::EventType type, QVariantMap &map, Network *network)
+    : IrcEvent(type, map, network)
+{
+    _exchangeType = static_cast<ExchangeType>(map.take("exchangeType").toInt());
+    _target = map.take("target").toString();
+    _key = map.take("key").toByteArray();
+}
+
+
+void KeyEvent::toVariantMap(QVariantMap &map) const
+{
+    IrcEvent::toVariantMap(map);
+    map["exchangeType"] = exchangeType();
+    map["target"] = target();
+    map["key"] = key();
+}
diff --git a/src/common/keyevent.h b/src/common/keyevent.h
new file mode 100644 (file)
index 0000000..0e7b3b8
--- /dev/null
@@ -0,0 +1,79 @@
+/***************************************************************************
+ *   Copyright (C) 2013 by the Quassel Project                             *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   You should have received a copy of the GNU General Public License     *
+ *   along with this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#ifndef KEYEVENT_H
+#define KEYEVENT_H
+
+#include "ircevent.h"
+
+class KeyEvent : public IrcEvent
+{
+public:
+    enum ExchangeType {
+        Init,
+        Finish
+    };
+
+    explicit KeyEvent(EventManager::EventType type, Network *network, const QString &prefix, const QString &target,
+        ExchangeType exchangeType, const QByteArray &key,
+        const QDateTime &timestamp = QDateTime())
+        : IrcEvent(type, network, prefix),
+        _exchangeType(exchangeType),
+        _target(target),
+        _key(key)
+    {
+        setTimestamp(timestamp);
+    }
+
+
+    inline ExchangeType exchangeType() const { return _exchangeType; }
+    inline void setExchangeType(ExchangeType type) { _exchangeType = type; }
+
+    inline QString target() const { return _target; }
+    inline void setTarget(const QString &target) { _target = target; }
+
+    inline QByteArray key() const { return _key; }
+    inline void setKey(const QByteArray &key) { _key = key; }
+
+    static Event *create(EventManager::EventType type, QVariantMap &map, Network *network);
+
+protected:
+    explicit KeyEvent(EventManager::EventType type, QVariantMap &map, Network *network);
+    void toVariantMap(QVariantMap &map) const;
+
+    virtual inline QString className() const { return "KeyEvent"; }
+    virtual inline void debugInfo(QDebug &dbg) const
+    {
+        NetworkEvent::debugInfo(dbg);
+        dbg << ", prefix = " << qPrintable(prefix())
+            << ", target = " << qPrintable(target())
+            << ", exchangetype = " << (exchangeType() == Init ? "init" : "finish")
+            << ", key = " << qPrintable(key());
+    }
+
+
+private:
+    ExchangeType _exchangeType;
+    QString _target;
+    QByteArray _key;
+};
+
+
+#endif
index 5ca3129..15a986b 100644 (file)
@@ -35,8 +35,10 @@ Cipher::~Cipher()
 
 bool Cipher::setKey(QByteArray key)
 {
-    if (key.isEmpty())
+    if (key.isEmpty()) {
+        m_key.clear();
         return false;
+    }
 
     if (key.mid(0, 4).toLower() == "ecb:")
     {
index a7e1b6c..99d1881 100644 (file)
@@ -59,13 +59,6 @@ void CoreIrcChannel::setEncrypted(bool e)
         if (topic().isEmpty())
             return;
 
-        QByteArray key = qobject_cast<CoreNetwork *>(network())->cipherKey(name());
-        if (key.isEmpty())
-            return;
-
-        if (!cipher()->setKey(key))
-            return;
-
         QByteArray decrypted = cipher()->decryptTopic(topic().toAscii());
         setTopic(decodeString(decrypted));
     }
index b734312..101f527 100644 (file)
@@ -310,7 +310,7 @@ void CoreNetwork::removeChannelKey(const QString &channel)
 
 
 #ifdef HAVE_QCA2
-Cipher *CoreNetwork::cipher(const QString &target) const
+Cipher *CoreNetwork::cipher(const QString &target)
 {
     if (target.isEmpty())
         return 0;
@@ -318,39 +318,51 @@ Cipher *CoreNetwork::cipher(const QString &target) const
     if (!Cipher::neededFeaturesAvailable())
         return 0;
 
-    QByteArray key = cipherKey(target);
-    if (key.isEmpty())
-        return 0;
-
     CoreIrcChannel *channel = qobject_cast<CoreIrcChannel *>(ircChannel(target));
     if (channel) {
-        if (channel->cipher()->setKey(key))
-            return channel->cipher();
+        return channel->cipher();
     }
-    else {
-        CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
-        if (user && user->cipher()->setKey(key))
-            return user->cipher();
+    CoreIrcUser *user = qobject_cast<CoreIrcUser *>(ircUser(target));
+    if (user) {
+        return user->cipher();
+    } else if (!isChannelName(target)) {
+        return qobject_cast<CoreIrcUser*>(newIrcUser(target))->cipher();
     }
     return 0;
 }
 
 
-QByteArray CoreNetwork::cipherKey(const QString &recipient) const
+QByteArray CoreNetwork::cipherKey(const QString &target) const
 {
-    return _cipherKeys.value(recipient.toLower(), QByteArray());
+    CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+    if (c)
+        return c->cipher()->key();
+
+    CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+    if (u)
+        return u->cipher()->key();
+
+    return QByteArray();
 }
 
 
-void CoreNetwork::setCipherKey(const QString &recipient, const QByteArray &key)
+void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
 {
-    if (!key.isEmpty())
-        _cipherKeys[recipient.toLower()] = key;
-    else
-        _cipherKeys.remove(recipient.toLower());
-}
+    CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
+    if (c) {
+        c->setEncrypted(c->cipher()->setKey(key));
+        return;
+    }
 
+    CoreIrcUser *u = qobject_cast<CoreIrcUser*>(ircUser(target));
+    if (!u && !isChannelName(target))
+        u = qobject_cast<CoreIrcUser*>(newIrcUser(target));
 
+    if (u) {
+        u->setEncrypted(u->cipher()->setKey(key));
+        return;
+    }
+}
 #endif /* HAVE_QCA2 */
 
 bool CoreNetwork::setAutoWhoDone(const QString &channel)
index 4757578..3b31f0d 100644 (file)
@@ -120,7 +120,7 @@ public slots:
 
     // Blowfish stuff
 #ifdef HAVE_QCA2
-    Cipher *cipher(const QString &recipient) const;
+    Cipher *cipher(const QString &recipient);
     QByteArray cipherKey(const QString &recipient) const;
     void setCipherKey(const QString &recipient, const QByteArray &key);
 #endif
index e2edd08..692257a 100644 (file)
 #include "netsplit.h"
 #include "quassel.h"
 
+#ifdef HAVE_QCA2
+#  include "keyevent.h"
+#endif
+
 CoreSessionEventProcessor::CoreSessionEventProcessor(CoreSession *session)
     : BasicHandler("handleCtcp", session),
     _coreSession(session)
@@ -436,6 +440,42 @@ void CoreSessionEventProcessor::processIrcEventTopic(IrcEvent *e)
 }
 
 
+#ifdef HAVE_QCA2
+void CoreSessionEventProcessor::processKeyEvent(KeyEvent *e)
+{
+    if (!Cipher::neededFeaturesAvailable()) {
+        emit newEvent(new MessageEvent(Message::Error, e->network(), tr("Unable to perform key exchange."), e->prefix(), e->target(), Message::None, e->timestamp()));
+        return;
+    }
+    CoreNetwork *net = qobject_cast<CoreNetwork*>(e->network());
+    Cipher *c = net->cipher(e->target());
+    if (!c) // happens when there is no CoreIrcChannel for the target (i.e. never?)
+        return;
+
+    if (e->exchangeType() == KeyEvent::Init) {
+        QByteArray pubKey = c->parseInitKeyX(e->key());
+        if (pubKey.isEmpty()) {
+            emit newEvent(new MessageEvent(Message::Error, e->network(), tr("Unable to parse the DH1080_INIT. Key exchange failed."), e->prefix(), e->target(), Message::None, e->timestamp()));
+            return;
+        } else {
+            net->setCipherKey(e->target(), c->key());
+            emit newEvent(new MessageEvent(Message::Info, e->network(), tr("Your key is set and messages will be encrypted."), e->prefix(), e->target(), Message::None, e->timestamp()));
+            QList<QByteArray> p;
+            p << net->serverEncode(e->target()) << net->serverEncode("DH1080_FINISH ")+pubKey;
+            net->putCmd("NOTICE", p);
+        }
+    } else {
+        if (c->parseFinishKeyX(e->key())) {
+            net->setCipherKey(e->target(), c->key());
+            emit newEvent(new MessageEvent(Message::Info, e->network(), tr("Your key is set and messages will be encrypted."), e->prefix(), e->target(), Message::None, e->timestamp()));
+        } else {
+            emit newEvent(new MessageEvent(Message::Info, e->network(), tr("Failed to parse DH1080_FINISH. Key exchange failed."), e->prefix(), e->target(), Message::None, e->timestamp()));
+        }
+    }
+}
+#endif
+
+
 /* RPL_WELCOME */
 void CoreSessionEventProcessor::processIrcEvent001(IrcEvent *e)
 {
index 680917a..6a163c6 100644 (file)
@@ -31,6 +31,10 @@ class IrcEvent;
 class IrcEventNumeric;
 class Netsplit;
 
+#ifdef HAVE_QCA2
+class KeyEvent;
+#endif
+
 class CoreSessionEventProcessor : public BasicHandler
 {
     Q_OBJECT
@@ -55,6 +59,9 @@ public:
     Q_INVOKABLE void processIrcEventQuit(IrcEvent *event);
     Q_INVOKABLE void lateProcessIrcEventQuit(IrcEvent *event);
     Q_INVOKABLE void processIrcEventTopic(IrcEvent *event);
+#ifdef HAVE_QCA2
+    Q_INVOKABLE void processKeyEvent(KeyEvent *event);
+#endif
 
     Q_INVOKABLE void processIrcEvent001(IrcEvent *event);          // RPL_WELCOME
     Q_INVOKABLE void processIrcEvent005(IrcEvent *event);          // RPL_ISUPPORT
index 05424a6..53f4c2e 100644 (file)
@@ -207,14 +207,6 @@ void CoreUserInputHandler::handleDelkey(const BufferInfo &bufferInfo, const QStr
     }
 
     network()->setCipherKey(target, QByteArray());
-
-    if (network()->isChannelName(target) && network()->channels().contains(target)) {
-        qobject_cast<CoreIrcChannel *>(network()->ircChannel(target))->setEncrypted(false);
-    }
-    else if (network()->nicks().contains(target)) {
-        qobject_cast<CoreIrcUser *>(network()->ircUser(target))->setEncrypted(false);
-    }
-
     emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("The key for %1 has been deleted.").arg(target));
 
 #else
@@ -327,6 +319,50 @@ void CoreUserInputHandler::handleJoin(const BufferInfo &bufferInfo, const QStrin
 }
 
 
+void CoreUserInputHandler::handleKeyx(const BufferInfo &bufferInfo, const QString &msg)
+{
+#ifdef HAVE_QCA2
+    if (!bufferInfo.isValid())
+        return;
+
+    if (!Cipher::neededFeaturesAvailable())
+        return;
+
+    QStringList parms = msg.split(' ', QString::SkipEmptyParts);
+
+    if (parms.count() == 0 && !bufferInfo.bufferName().isEmpty())
+        parms.prepend(bufferInfo.bufferName());
+    else if (parms.count() != 1) {
+        emit displayMsg(Message::Info, bufferInfo.bufferName(),
+            tr("[usage] /keyx [<nick|channel>] Initiates a DH1080 key exchange with the target."));
+        return;
+    }
+
+    QString target = parms.at(0);
+
+    Cipher *cipher = network()->cipher(target);
+    if (!cipher) // happens when there is no CoreIrcChannel for the target
+        return;
+
+    QByteArray pubKey = cipher->initKeyExchange();
+    if (pubKey.isEmpty())
+        emit displayMsg(Message::Error, bufferInfo.bufferName(), tr("Failed to initiate key exchange with %1.").arg(target));
+    else {
+        QList<QByteArray> params;
+        params << serverEncode(target) << serverEncode("DH1080_INIT ") + pubKey;
+        emit putCmd("NOTICE", params);
+        emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("Initiated key exchange with %1.").arg(target));
+    }
+#else
+    Q_UNUSED(msg)
+    emit displayMsg(Message::Error, bufferInfo.bufferName(), tr("Error: Setting an encryption key requires Quassel to have been built "
+                                                                "with support for the Qt Cryptographic Architecture (QCA) library. "
+                                                                "Contact your distributor about a Quassel package with QCA "
+                                                                "support, or rebuild Quassel with QCA present."));
+#endif
+}
+
+
 void CoreUserInputHandler::handleKick(const BufferInfo &bufferInfo, const QString &msg)
 {
     QString nick = msg.section(' ', 0, 0, QString::SectionSkipEmpty);
@@ -560,14 +596,8 @@ void CoreUserInputHandler::handleSetkey(const BufferInfo &bufferInfo, const QStr
 
     QString target = parms.at(0);
     QByteArray key = parms.at(1).toLocal8Bit();
-
     network()->setCipherKey(target, key);
 
-    if (network()->isChannelName(target) && network()->channels().contains(target))
-        qobject_cast<CoreIrcChannel *>(network()->ircChannel(target))->setEncrypted(true);
-    else if (network()->nicks().contains(target))
-        qobject_cast<CoreIrcUser *>(network()->ircUser(target))->setEncrypted(true);
-
     emit displayMsg(Message::Info, bufferInfo.bufferName(), tr("The key for %1 has been set.").arg(target));
 #else
     Q_UNUSED(msg)
@@ -716,7 +746,7 @@ void CoreUserInputHandler::putPrivmsg(const QByteArray &target, const QByteArray
         QByteArray crypted = message.left(splitPos);
         bool isEncrypted = false;
 #ifdef HAVE_QCA2
-        if (cipher && !message.isEmpty()) {
+        if (cipher && !cipher->key().isEmpty() && !message.isEmpty()) {
             isEncrypted = cipher->encrypt(crypted);
         }
 #endif
@@ -797,7 +827,7 @@ QByteArray CoreUserInputHandler::encrypt(const QString &target, const QByteArray
         return message_;
 
     Cipher *cipher = network()->cipher(target);
-    if (!cipher)
+    if (!cipher || cipher->key().isEmpty())
         return message_;
 
     QByteArray message = message_;
index b6ebac1..ccd9bd2 100644 (file)
@@ -48,6 +48,7 @@ public slots:
     void handleDevoice(const BufferInfo &bufferInfo, const QString &text);
     void handleInvite(const BufferInfo &bufferInfo, const QString &text);
     void handleJoin(const BufferInfo &bufferInfo, const QString &text);
+    void handleKeyx(const BufferInfo &bufferInfo, const QString &text);
     void handleKick(const BufferInfo &bufferInfo, const QString &text);
     void handleKill(const BufferInfo &bufferInfo, const QString &text);
     void handleList(const BufferInfo &bufferInfo, const QString &text);
index e69336f..f3db4fc 100644 (file)
@@ -28,6 +28,7 @@
 
 #ifdef HAVE_QCA2
 #  include "cipher.h"
+#  include "keyevent.h"
 #endif
 
 IrcParser::IrcParser(CoreSession *session) :
@@ -58,7 +59,7 @@ QByteArray IrcParser::decrypt(Network *network, const QString &bufferName, const
         return message;
 
     Cipher *cipher = qobject_cast<CoreNetwork *>(network)->cipher(bufferName);
-    if (!cipher)
+    if (!cipher || cipher->key().isEmpty())
         return message;
 
     return isTopic ? cipher->decryptTopic(message) : cipher->decrypt(message);
@@ -228,7 +229,16 @@ void IrcParser::processNetworkIncoming(NetworkDataEvent *e)
                     if (!net->isChannelName(target))
                         target = nickFromMask(prefix);
                 }
-                events << new IrcEventRawMessage(EventManager::IrcEventRawNotice, net, params[1], prefix, target, e->timestamp());
+
+#ifdef HAVE_QCA2
+                // Handle DH1080 key exchange
+                if (params[1].startsWith("DH1080_INIT")) {
+                    events << new KeyEvent(EventManager::KeyEvent, net, prefix, target, KeyEvent::Init, params[1].mid(12));
+                } else if (params[1].startsWith("DH1080_FINISH")) {
+                    events << new KeyEvent(EventManager::KeyEvent, net, prefix, target, KeyEvent::Finish, params[1].mid(14));
+                } else
+#endif
+                    events << new IrcEventRawMessage(EventManager::IrcEventRawNotice, net, params[1], prefix, target, e->timestamp());
             }
         }
         break;