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

1  2 
src/common/CMakeLists.txt
src/common/eventmanager.h
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

@@@ -17,7 -17,7 +17,7 @@@ set(SOURCE
      eventmanager.cpp
      identity.cpp
      ignorelistmanager.cpp
 -    internalconnection.cpp
 +    internalpeer.cpp
      ircchannel.cpp
      ircevent.cpp
      irclisthelper.cpp
      networkconfig.cpp
      networkevent.cpp
      quassel.cpp
 -    remoteconnection.cpp
 +    remotepeer.cpp
      settings.cpp
      signalproxy.cpp
      syncableobject.cpp
      util.cpp
  
 -    protocols/legacy/legacyconnection.cpp
 +    protocols/legacy/legacypeer.cpp
  )
  
  set(MOC_HDRS
      eventmanager.h
      identity.h
      ignorelistmanager.h
 -    internalconnection.h
 +    internalpeer.h
      ircchannel.h
      irclisthelper.h
      ircuser.h
      network.h
      networkconfig.h
 -    remoteconnection.h
 +    remotepeer.h
      settings.h
      signalproxy.h
      syncableobject.h
  
 -    protocols/legacy/legacyconnection.h
 +    protocols/legacy/legacypeer.h
  )
  
  set(HEADERS ${MOC_HDRS}
      networkevent.h
      logger.h
      message.h
 +    protocol.h
      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)
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -113,6 -113,10 +113,10 @@@ public 
  
          CtcpEvent                   = 0x00050000,
          CtcpEventFlush
+ #ifdef HAVE_QCA2
+         ,KeyEvent                    = 0x00060000
+ #endif
      };
  
      EventManager(QObject *parent = 0);
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -59,13 -59,6 +59,6 @@@ void CoreIrcChannel::setEncrypted(bool 
          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));
      }
diff --combined src/core/corenetwork.cpp
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -310,7 -310,7 +310,7 @@@ void CoreNetwork::removeChannelKey(cons
  
  
  #ifdef HAVE_QCA2
- Cipher *CoreNetwork::cipher(const QString &target) const
+ Cipher *CoreNetwork::cipher(const QString &target)
  {
      if (target.isEmpty())
          return 0;
      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)
diff --combined src/core/corenetwork.h
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -120,7 -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
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
  #include "netsplit.h"
  #include "quassel.h"
  
+ #ifdef HAVE_QCA2
+ #  include "keyevent.h"
+ #endif
  CoreSessionEventProcessor::CoreSessionEventProcessor(CoreSession *session)
      : BasicHandler("handleCtcp", session),
      _coreSession(session)
@@@ -112,22 -116,14 +116,22 @@@ void CoreSessionEventProcessor::process
  
      CoreNetwork *net = coreNetwork(e);
  
 -    QString construct = net->saslAccount();
 -    construct.append(QChar(QChar::Null));
 -    construct.append(net->saslAccount());
 -    construct.append(QChar(QChar::Null));
 -    construct.append(net->saslPassword());
 -    QByteArray saslData = QByteArray(construct.toAscii().toBase64());
 -    saslData.prepend("AUTHENTICATE ");
 -    net->putRawLine(saslData);
 +#ifdef HAVE_SSL
 +    if (net->identityPtr()->sslCert().isNull()) {
 +#endif
 +        QString construct = net->saslAccount();
 +        construct.append(QChar(QChar::Null));
 +        construct.append(net->saslAccount());
 +        construct.append(QChar(QChar::Null));
 +        construct.append(net->saslPassword());
 +        QByteArray saslData = QByteArray(construct.toAscii().toBase64());
 +        saslData.prepend("AUTHENTICATE ");
 +        net->putRawLine(saslData);
 +#ifdef HAVE_SSL
 +    } else {
 +        net->putRawLine("AUTHENTICATE +");
 +    }
 +#endif
  }
  
  
@@@ -137,19 -133,9 +141,19 @@@ void CoreSessionEventProcessor::process
      // additional CAP messages (ls, multi-prefix, et cetera).
  
      if (e->params().count() == 3) {
 -        if (e->params().at(2) == "sasl") {
 +        if (e->params().at(2).startsWith("sasl")) { // Freenode (at least) sends "sasl " with a trailing space for some reason!
              // FIXME use event
 -            coreNetwork(e)->putRawLine(coreNetwork(e)->serverEncode("AUTHENTICATE PLAIN")); // Only working with PLAIN atm, blowfish later
 +            // if the current identity has a cert set, use SASL EXTERNAL
 +#ifdef HAVE_SSL
 +            if (!coreNetwork(e)->identityPtr()->sslCert().isNull()) {
 +                coreNetwork(e)->putRawLine(coreNetwork(e)->serverEncode("AUTHENTICATE EXTERNAL"));
 +            } else {
 +#endif
 +                // Only working with PLAIN atm, blowfish later
 +                coreNetwork(e)->putRawLine(coreNetwork(e)->serverEncode("AUTHENTICATE PLAIN"));
 +#ifdef HAVE_SSL
 +            }
 +#endif
          }
      }
  }
@@@ -436,6 -422,42 +440,42 @@@ void CoreSessionEventProcessor::process
  }
  
  
+ #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)
  {
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -31,6 -31,10 +31,10 @@@ class IrcEvent
  class IrcEventNumeric;
  class Netsplit;
  
+ #ifdef HAVE_QCA2
+ class KeyEvent;
+ #endif
  class CoreSessionEventProcessor : public BasicHandler
  {
      Q_OBJECT
@@@ -55,6 -59,9 +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
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -207,14 -207,6 +207,6 @@@ void CoreUserInputHandler::handleDelkey
      }
  
      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 +319,50 @@@ void CoreUserInputHandler::handleJoin(c
  }
  
  
+ 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 +596,8 @@@ void CoreUserInputHandler::handleSetkey
  
      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 +746,7 @@@ void CoreUserInputHandler::putPrivmsg(c
          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 +827,7 @@@ QByteArray CoreUserInputHandler::encryp
          return message_;
  
      Cipher *cipher = network()->cipher(target);
-     if (!cipher)
+     if (!cipher || cipher->key().isEmpty())
          return message_;
  
      QByteArray message = message_;
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -48,6 -48,7 +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);
diff --combined src/core/ircparser.cpp
@@@ -1,5 -1,5 +1,5 @@@
  /***************************************************************************
 - *   Copyright (C) 2005-2012 by the Quassel Project                        *
 + *   Copyright (C) 2005-2013 by the Quassel Project                        *
   *   devel@quassel-irc.org                                                 *
   *                                                                         *
   *   This program is free software; you can redistribute it and/or modify  *
@@@ -28,6 -28,7 +28,7 @@@
  
  #ifdef HAVE_QCA2
  #  include "cipher.h"
+ #  include "keyevent.h"
  #endif
  
  IrcParser::IrcParser(CoreSession *session) :
@@@ -58,7 -59,7 +59,7 @@@ QByteArray IrcParser::decrypt(Network *
          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 +229,16 @@@ void IrcParser::processNetworkIncoming(
                      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;