Implemented SSL support for client <-> core connection.
authorMarcus Eggenberger <egs@quassel-irc.org>
Sun, 30 Mar 2008 13:41:13 +0000 (13:41 +0000)
committerMarcus Eggenberger <egs@quassel-irc.org>
Sun, 30 Mar 2008 13:41:13 +0000 (13:41 +0000)
To make this work the core needs a ssl certificate in pem format using
an rsa key. This certificate needs to reside in ~/.quassel/quasselCert.pem

If you don't have a certificate, you can create one using openssl:
  openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout ~/.quassel/quasselCert.pem -out ~/.quassel/quasselCert.pem

13 files changed:
src/client/clientsyncer.cpp
src/client/clientsyncer.h
src/common/util.cpp
src/common/util.h
src/core/core.cpp
src/core/core.h
src/core/core.pri
src/core/sqlitestorage.cpp
src/qtui/coreconnectdlg.cpp
src/qtui/coreconnectdlg.h
src/qtui/ui/coreaccounteditdlg.ui
src/qtui/ui/coreconnectdlg.ui
version.inc

index 9534b6f..952860a 100644 (file)
@@ -87,6 +87,7 @@ void ClientSyncer::coreHasData() {
 }
 
 void ClientSyncer::coreSocketError(QAbstractSocket::SocketError) {
+  qDebug() << "coreSocketError" << socket << socket->errorString();
   emit connectionError(socket->errorString());
   socket->deleteLater();
 }
@@ -120,7 +121,13 @@ void ClientSyncer::connectToCore(const QVariantMap &conn) {
     //clientMode = RemoteCore;
     //emit coreConnectionMsg(tr("Connecting..."));
     Q_ASSERT(!socket);
+
+#ifndef QT_NO_OPENSSL
+    QSslSocket *sock = new QSslSocket(Client::instance());
+#else
     QTcpSocket *sock = new QTcpSocket(Client::instance());
+#endif
+
     if(conn.contains("useProxy") && conn["useProxy"].toBool()) {
       QNetworkProxy proxy((QNetworkProxy::ProxyType)conn["proxyType"].toInt(), conn["proxyHost"].toString(), conn["proxyPort"].toUInt(), conn["proxyUser"].toString(), conn["proxyPassword"].toString());
       sock->setProxy(proxy);
@@ -145,7 +152,8 @@ void ClientSyncer::coreSocketConnected() {
   clientInit["ClientVersion"] = Global::quasselVersion;
   clientInit["ClientDate"] = Global::quasselDate;
   clientInit["ClientBuild"] = Global::quasselBuild; // this is a minimum, since we probably won't update for every commit
-  clientInit["UseSsl"] = false;  // FIXME implement SSL
+  clientInit["UseSsl"] = coreConnectionInfo["useSsl"];
+  
   SignalProxy::writeDataToDevice(socket, clientInit);
 }
 
@@ -172,6 +180,27 @@ void ClientSyncer::clientInitAck(const QVariantMap &msg) {
     return;
   }
   emit connectionMsg(msg["CoreInfo"].toString());
+  if(coreConnectionInfo["useSsl"].toBool()) {
+    if(msg["SupportSsl"].toBool()) {
+      QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket);
+      if(sslSocket) {
+       connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
+       sslSocket->startClientEncryption();
+       emit encrypted(true);
+      } else {
+       emit connectionError(tr("<b>This client is built without SSL Support!</b><br />Disable the usage of SSL in the account settings."));
+       emit encrypted(false);
+       disconnectFromCore();
+       return;
+      }
+    } else {
+      emit connectionError(tr("<b>The Quassel Core you are trying to connect to does not support SSL!</b><br />If you want to connect anyways, disable the usage of SSL in the account settings."));
+      emit encrypted(false);
+      disconnectFromCore();
+      return;
+    }
+  }
+
   if(!msg["Configured"].toBool()) {
     // start wizard
     emit startCoreSetup(msg["StorageBackends"].toList());
@@ -330,3 +359,12 @@ void ClientSyncer::checkSyncState() {
   }
 }
 
+void ClientSyncer::sslErrors(const QList<QSslError> &errors) {
+  qDebug() << "SSL Errors:";
+  foreach(QSslError err, errors)
+    qDebug() << "  " << err;
+
+  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
+  if(socket)
+    socket->ignoreSslErrors();
+}
index 95aee59..5ea2ae4 100644 (file)
@@ -24,6 +24,7 @@
 #include <QPointer>
 #include <QString>
 #include <QTcpSocket>
+#include <QSslSocket>
 #include <QVariantMap>
 
 class IrcUser;
@@ -55,6 +56,7 @@ class ClientSyncer : public QObject {
     void coreSetupSuccess();
     void coreSetupFailed(const QString &error);
 
+    void encrypted(bool);
 
   public slots:
     void connectToCore(const QVariantMap &);
@@ -83,7 +85,8 @@ class ClientSyncer : public QObject {
     void sessionStateReceived(const QVariantMap &state);
 
     void doCoreSetup(const QVariant &setupData);
-
+    void sslErrors(const QList<QSslError> &errors);
+  
   private:
     QPointer<QIODevice> socket;
     quint32 blockSize;
index e47882a..aeccf67 100644 (file)
@@ -138,3 +138,18 @@ QByteArray methodName(const QMetaMethod &method) {
   QByteArray sig(method.signature());
   return sig.left(sig.indexOf("("));
 }
+
+QDir quasselDir() {
+  // kinda ugly, but I currently see no other way to do that
+#ifdef Q_OS_WIN32
+  QString quasselDir = QDir::homePath() + qgetenv("APPDATA") + "/quassel/";
+#else
+  QString quasselDir = QDir::homePath() + "/.quassel/";
+#endif
+
+  QDir qDir(quasselDir);
+  if(!qDir.exists(quasselDir))
+    qDir.mkpath(quasselDir);
+
+  return qDir;
+}
index 9354620..0b372b6 100644 (file)
@@ -21,6 +21,7 @@
 #ifndef _UTIL_H_
 #define _UTIL_H_
 
+#include <QDir>
 #include <QIODevice>
 #include <QVariant>
 #include <QString>
@@ -51,4 +52,6 @@ uint editingDistance(const QString &s1, const QString &s2);
 
 QByteArray methodName(const QMetaMethod &method);
 
+QDir quasselDir();
+
 #endif
index 20dc449..50bfa73 100644 (file)
@@ -30,6 +30,8 @@
 #include "sqlitestorage.h"
 #include "network.h"
 
+#include "util.h"
+
 Core *Core::instanceptr = 0;
 QMutex Core::mutex;
 
@@ -337,6 +339,8 @@ void Core::incomingConnection() {
     QTcpSocket *socket = server.nextPendingConnection();
     connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
     connect(socket, SIGNAL(readyRead()), this, SLOT(clientHasData()));
+    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
+    
     QVariantMap clientInfo;
     blocksizes.insert(socket, (quint32)0);
     qDebug() << "Client connected from"  << qPrintable(socket->peerAddress().toString());
@@ -381,7 +385,12 @@ void Core::processClientMessage(QTcpSocket *socket, const QVariantMap &msg) {
                             "Up %3d%4h%5m (since %6)").arg(Global::quasselVersion).arg(Global::quasselBuild)
                             .arg(updays).arg(uphours,2,10,QChar('0')).arg(upmins,2,10,QChar('0')).arg(startTime.toString(Qt::TextDate));
 
-    reply["SupportSsl"] = false;
+    SslServer *sslServer = qobject_cast<SslServer *>(&server);
+    QSslSocket *sslSocket = qobject_cast<QSslSocket *>(socket);
+    bool supportSsl = (bool)sslServer && (bool)sslSocket && sslServer->certIsValid();
+    reply["SupportSsl"] = supportSsl;
+    // switch to ssl after client has been informed about our capabilities (see below)
+
     reply["LoginEnabled"] = true;
 
     // Just version information -- check it!
@@ -412,6 +421,15 @@ void Core::processClientMessage(QTcpSocket *socket, const QVariantMap &msg) {
     clientInfo[socket] = msg; // store for future reference
     reply["MsgType"] = "ClientInitAck";
     SignalProxy::writeDataToDevice(socket, reply);
+
+    // after we told the client that we are ssl capable we switch to ssl mode
+    if(supportSsl && msg["UseSsl"].toBool()) {
+      qDebug() << "Starting TLS for Client:"  << qPrintable(socket->peerAddress().toString());
+      connect(sslSocket, SIGNAL(sslErrors(const QList<QSslError> &)), this, SLOT(sslErrors(const QList<QSslError> &)));
+      sslSocket->startServerEncryption();
+    }
+    
+
   } else {
     // for the rest, we need an initialized connection
     if(!clientInfo.contains(socket)) {
@@ -496,3 +514,16 @@ SessionThread *Core::createSession(UserId uid, bool restore) {
   sess->start();
   return sess;
 }
+
+void Core::sslErrors(const QList<QSslError> &errors) {
+  Q_UNUSED(errors);
+  QSslSocket *socket = qobject_cast<QSslSocket *>(sender());
+  if(socket)
+    socket->ignoreSslErrors();
+}
+
+void Core::socketError(QAbstractSocket::SocketError err) {
+  QAbstractSocket *socket = qobject_cast<QAbstractSocket *>(sender());
+  if(socket && err != QAbstractSocket::RemoteHostClosedError)
+    qDebug() << "Core::socketError()" << socket << err << socket->errorString();
+}
index 19d6541..277c43c 100644 (file)
 #include <QString>
 #include <QVariant>
 #include <QTimer>
-#include <QTcpServer>
 #include <QTcpSocket>
+#include <QSslSocket>
 
 #include "bufferinfo.h"
 #include "message.h"
 #include "global.h"
 #include "sessionthread.h"
+#include "sslserver.h"
 #include "types.h"
 
 class CoreSession;
@@ -270,6 +271,9 @@ class Core : public QObject {
 
     bool initStorage(QVariantMap dbSettings, bool setup = false);
 
+    void sslErrors(const QList<QSslError> &errors);
+    void socketError(QAbstractSocket::SocketError);
+
   private:
     Core();
     ~Core();
@@ -289,7 +293,12 @@ class Core : public QObject {
     Storage *storage;
     QTimer _storageSyncTimer;
 
-    QTcpServer server; // TODO: implement SSL
+#ifndef QT_NO_OPENSSL  
+    SslServer server;
+#else
+    QTcpServer server;
+#endif  
+
     QHash<QTcpSocket *, quint32> blocksizes;
     QHash<QTcpSocket *, QVariantMap> clientInfo;
 
index 722c530..78087d1 100644 (file)
@@ -1,6 +1,6 @@
 DEPMOD = common
 QT_MOD = core network sql script
 SRCS = core.cpp corebacklogmanager.cpp corebufferviewconfig.cpp corebufferviewmanager.cpp coresession.cpp coresettings.cpp networkconnection.cpp sqlitestorage.cpp abstractsqlstorage.cpp storage.cpp basichandler.cpp \
-       ircserverhandler.cpp userinputhandler.cpp ctcphandler.cpp coreusersettings.cpp sessionthread.cpp
+       ircserverhandler.cpp userinputhandler.cpp ctcphandler.cpp coreusersettings.cpp sessionthread.cpp sslserver.cpp
 HDRS = core.h corebacklogmanager.h corebufferviewconfig.h corebufferviewmanager.h coresession.h coresettings.h networkconnection.h sqlitestorage.h abstractsqlstorage.h storage.h basichandler.h \
-       ircserverhandler.h userinputhandler.h ctcphandler.h coreusersettings.h sessionthread.h
+       ircserverhandler.h userinputhandler.h ctcphandler.h coreusersettings.h sessionthread.h sslserver.h
index fb90452..2121f79 100644 (file)
@@ -24,6 +24,8 @@
 
 #include "network.h"
 
+#include "util.h"
+
 SqliteStorage::SqliteStorage(QObject *parent)
   : AbstractSqlStorage(parent)
 {
@@ -723,18 +725,7 @@ QList<Message> SqliteStorage::requestMsgRange(UserId user, BufferId bufferId, in
 }
 
 QString SqliteStorage::backlogFile() {
-  // kinda ugly, but I currently see no other way to do that
-#ifdef Q_OS_WIN32
-  QString quasselDir = QDir::homePath() + qgetenv("APPDATA") + "\\quassel\\";
-#else
-  QString quasselDir = QDir::homePath() + "/.quassel/";
-#endif
-
-  QDir qDir(quasselDir);
-  if(!qDir.exists(quasselDir))
-    qDir.mkpath(quasselDir);
-  
-  return quasselDir + "quassel-storage.sqlite";  
+  return quasselDir().absolutePath() + "/quassel-storage.sqlite";  
 }
 
 
index fd4e590..b4225ef 100644 (file)
@@ -63,6 +63,7 @@ CoreConnectDlg::CoreConnectDlg(QWidget *parent, bool autoconnect) : QDialog(pare
   connect(clientSyncer, SIGNAL(socketStateChanged(QAbstractSocket::SocketState)),this, SLOT(initPhaseSocketState(QAbstractSocket::SocketState)));
   connect(clientSyncer, SIGNAL(connectionError(const QString &)), this, SLOT(initPhaseError(const QString &)));
   connect(clientSyncer, SIGNAL(connectionMsg(const QString &)), this, SLOT(initPhaseMsg(const QString &)));
+  connect(clientSyncer, SIGNAL(encrypted(bool)), this, SLOT(encrypted(bool)));
   connect(clientSyncer, SIGNAL(startLogin()), this, SLOT(startLogin()));
   connect(clientSyncer, SIGNAL(loginFailed(const QString &)), this, SLOT(loginFailed(const QString &)));
   connect(clientSyncer, SIGNAL(loginSuccess()), this, SLOT(startSync()));
@@ -209,6 +210,7 @@ void CoreConnectDlg::on_accountButtonBox_accepted() {
 /*** Phase One: initializing the core connection ***/
 
 void CoreConnectDlg::connectToCore() {
+  ui.secureConnection->hide();
   ui.connectIcon->setPixmap(QPixmap::fromImage(QImage(":/22x22/actions/network-disconnect")));
   ui.connectLabel->setText(tr("Connect to %1").arg(accountData["Host"].toString()));
   ui.coreInfoLabel->setText("");
@@ -224,6 +226,7 @@ void CoreConnectDlg::connectToCore() {
 
 void CoreConnectDlg::initPhaseError(const QString &error) {
   doingAutoConnect = false;
+  ui.secureConnection->hide();
   ui.connectIcon->setPixmap(QPixmap::fromImage(QImage(":/22x22/status/dialog-error")));
   //ui.connectLabel->setBrush(QBrush("red"));
   ui.connectLabel->setText(tr("<div style=color:red;>Connection to %1 failed!</div>").arg(accountData["Host"].toString()));
@@ -239,6 +242,13 @@ void CoreConnectDlg::initPhaseMsg(const QString &msg) {
   ui.coreInfoLabel->setText(msg);
 }
 
+void CoreConnectDlg::encrypted(bool useSsl) {
+  if(useSsl)
+    ui.secureConnection->show();
+  else
+    ui.secureConnection->hide();
+}
+
 void CoreConnectDlg::initPhaseSocketState(QAbstractSocket::SocketState state) {
   QString s;
   QString host = accountData["Host"].toString();
@@ -469,6 +479,7 @@ CoreAccountEditDlg::CoreAccountEditDlg(AccountId id, const QVariantMap &acct, co
     ui.port->setValue(acct["Port"].toUInt());
     ui.useInternal->setChecked(acct["UseInternal"].toBool());
     ui.accountName->setText(acct["AccountName"].toString());
+    ui.useSsl->setChecked(account["useSsl"].toBool());
     ui.useProxy->setChecked(account["useProxy"].toBool());
     ui.proxyHost->setText(account["proxyHost"].toString());
     ui.proxyPort->setValue(account["proxyPort"].toUInt());
@@ -485,6 +496,7 @@ QVariantMap CoreAccountEditDlg::accountData() {
   account["Host"] = ui.host->text().trimmed();
   account["Port"] = ui.port->value();
   account["UseInternal"] = ui.useInternal->isChecked();
+  account["useSsl"] = ui.useSsl->isChecked();
   account["useProxy"] = ui.useProxy->isChecked();
   account["proxyHost"] = ui.proxyHost->text().trimmed();
   account["proxyPort"] = ui.proxyPort->value();
index 42638e5..0877247 100644 (file)
@@ -61,6 +61,7 @@ class CoreConnectDlg : public QDialog {
     void initPhaseError(const QString &error);
     void initPhaseMsg(const QString &msg);
     void initPhaseSocketState(QAbstractSocket::SocketState);
+    void encrypted(bool);
 
     /*** Phase Two: Login ***/
     void startLogin();
index 5dda97d..30364a2 100644 (file)
            </widget>
           </item>
           <item row="3" column="1" colspan="2" >
-           <widget class="QCheckBox" name="useSSL" >
-            <property name="enabled" >
-             <bool>false</bool>
-            </property>
+           <widget class="QCheckBox" name="useSsl" >
             <property name="text" >
              <string>Use secure connection (SSL)</string>
             </property>
index d5a41a6..8204ac9 100644 (file)
@@ -28,7 +28,7 @@
       </sizepolicy>
      </property>
      <property name="currentIndex" >
-      <number>0</number>
+      <number>1</number>
      </property>
      <widget class="QWidget" name="accountPage" >
       <layout class="QVBoxLayout" >
@@ -207,6 +207,16 @@ space</string>
               </property>
              </widget>
             </item>
+            <item row="0" column="3" >
+             <widget class="QLabel" name="secureConnection" >
+              <property name="text" >
+               <string/>
+              </property>
+              <property name="pixmap" >
+               <pixmap resource="../../icons/icons.qrc" >:/22x22/actions/oxygen/22x22/actions/document-encrypt.png</pixmap>
+              </property>
+             </widget>
+            </item>
            </layout>
           </item>
           <item>
index 45df457..fdbab3c 100644 (file)
@@ -4,8 +4,8 @@
 { using namespace Global;
 
   quasselVersion = "0.2.0-alpha4-pre";
-  quasselDate = "2008-03-29";
-  quasselBuild = 664;
+  quasselDate = "2008-03-30";
+  quasselBuild = 668;
 
   //! Minimum client build number the core needs
   clientBuildNeeded = 642;