Persist Blowfish keys in the database
authorMichael Marley <michael@michaelmarley.com>
Tue, 8 May 2018 18:49:33 +0000 (13:49 -0500)
committerManuel Nickschas <sputnick@quassel-irc.org>
Wed, 23 May 2018 22:33:28 +0000 (00:33 +0200)
Add a new text field called "cipher" and store the hex-encoded
cipher key there whenever a new key is set or exchanged.  Also,
when each network is initialized, load the ciphers out of the
database and initialize the in-memory hashmap.  Then, the existing
behavior of each CoreIrcNetwork automatically using these keys
upon construction occurs.

Additionally, this makes PM buffer ciphers persistent both across
destruction/construction and across core restarts.

Note that the existing "key" field in the database is confusingly
named.  It does not contain any sort of cryptographic key but
instead holds channel passwords.

Closes #1473

Closes GH-332.

22 files changed:
src/core/SQL/PostgreSQL/migrate_write_buffer.sql
src/core/SQL/PostgreSQL/select_buffer_ciphers.sql [new file with mode: 0644]
src/core/SQL/PostgreSQL/setup_050_buffer.sql
src/core/SQL/PostgreSQL/update_buffer_cipher.sql [new file with mode: 0644]
src/core/SQL/PostgreSQL/version/25/upgrade_000_alter_buffer_add_cipher.sql [new file with mode: 0644]
src/core/SQL/SQLite/migrate_read_buffer.sql
src/core/SQL/SQLite/select_buffer_ciphers.sql [new file with mode: 0644]
src/core/SQL/SQLite/setup_030_buffer.sql
src/core/SQL/SQLite/update_buffer_cipher.sql [new file with mode: 0644]
src/core/SQL/SQLite/version/27/upgrade_000_alter_buffer_add_cipher.sql [new file with mode: 0644]
src/core/abstractsqlstorage.h
src/core/core.h
src/core/coreircuser.cpp
src/core/corenetwork.cpp
src/core/coresession.cpp
src/core/coresession.h
src/core/postgresqlstorage.cpp
src/core/postgresqlstorage.h
src/core/sql.qrc
src/core/sqlitestorage.cpp
src/core/sqlitestorage.h
src/core/storage.h

index ba80880..3ae6cf4 100644 (file)
@@ -1,2 +1,2 @@
-INSERT INTO buffer (bufferid, userid, groupid, networkid, buffername, buffercname, buffertype, lastmsgid, lastseenmsgid, markerlinemsgid, bufferactivity, key, joined)
-VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+INSERT INTO buffer (bufferid, userid, groupid, networkid, buffername, buffercname, buffertype, lastmsgid, lastseenmsgid, markerlinemsgid, bufferactivity, key, joined, cipher)
+VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
diff --git a/src/core/SQL/PostgreSQL/select_buffer_ciphers.sql b/src/core/SQL/PostgreSQL/select_buffer_ciphers.sql
new file mode 100644 (file)
index 0000000..45120d6
--- /dev/null
@@ -0,0 +1,3 @@
+SELECT buffername, cipher
+FROM buffer
+WHERE userid = :userid AND networkid = :networkid AND buffertype IN (2, 4)
index a10265b..0d92f43 100644 (file)
@@ -12,6 +12,7 @@ create TABLE buffer (
        bufferactivity integer NOT NULL DEFAULT 0,
        key varchar(128),
        joined boolean NOT NULL DEFAULT FALSE, -- BOOL
+       cipher TEXT,
        UNIQUE(userid, networkid, buffercname),
        CHECK (buffer.lastseenmsgid <= buffer.lastmsgid)
 )
diff --git a/src/core/SQL/PostgreSQL/update_buffer_cipher.sql b/src/core/SQL/PostgreSQL/update_buffer_cipher.sql
new file mode 100644 (file)
index 0000000..f2017c4
--- /dev/null
@@ -0,0 +1,3 @@
+UPDATE buffer
+SET cipher = :cipher
+WHERE userid = :userid AND networkid = :networkid AND buffercname = :buffercname
diff --git a/src/core/SQL/PostgreSQL/version/25/upgrade_000_alter_buffer_add_cipher.sql b/src/core/SQL/PostgreSQL/version/25/upgrade_000_alter_buffer_add_cipher.sql
new file mode 100644 (file)
index 0000000..15b2937
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE buffer
+ADD COLUMN cipher TEXT
index 5d20037..da20e39 100644 (file)
@@ -1,2 +1,2 @@
-SELECT bufferid, userid, groupid, networkid, buffername, buffercname, buffertype, lastmsgid, lastseenmsgid, markerlinemsgid, bufferactivity, key, joined
+SELECT bufferid, userid, groupid, networkid, buffername, buffercname, buffertype, lastmsgid, lastseenmsgid, markerlinemsgid, bufferactivity, key, joined, cipher
 FROM buffer
diff --git a/src/core/SQL/SQLite/select_buffer_ciphers.sql b/src/core/SQL/SQLite/select_buffer_ciphers.sql
new file mode 100644 (file)
index 0000000..45120d6
--- /dev/null
@@ -0,0 +1,3 @@
+SELECT buffername, cipher
+FROM buffer
+WHERE userid = :userid AND networkid = :networkid AND buffertype IN (2, 4)
index 3e09d06..5781c24 100644 (file)
@@ -12,5 +12,6 @@ CREATE TABLE buffer (
        bufferactivity INTEGER NOT NULL DEFAULT 0,
        key TEXT,
        joined INTEGER NOT NULL DEFAULT 0, -- BOOL
+       cipher TEXT,
        CHECK (lastseenmsgid <= lastmsgid)
 )
diff --git a/src/core/SQL/SQLite/update_buffer_cipher.sql b/src/core/SQL/SQLite/update_buffer_cipher.sql
new file mode 100644 (file)
index 0000000..f2017c4
--- /dev/null
@@ -0,0 +1,3 @@
+UPDATE buffer
+SET cipher = :cipher
+WHERE userid = :userid AND networkid = :networkid AND buffercname = :buffercname
diff --git a/src/core/SQL/SQLite/version/27/upgrade_000_alter_buffer_add_cipher.sql b/src/core/SQL/SQLite/version/27/upgrade_000_alter_buffer_add_cipher.sql
new file mode 100644 (file)
index 0000000..15b2937
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE buffer
+ADD COLUMN cipher TEXT
index 9bcd286..c554d9a 100644 (file)
@@ -236,6 +236,7 @@ public:
         int bufferactivity;
         QString key;
         bool joined;
+        QString cipher;
     };
 
     struct BacklogMO {
index 5f90917..98e2612 100644 (file)
@@ -273,6 +273,33 @@ public:
     }
 
 
+    //! Get a hash of buffers with their ciphers for a given network
+    /** The keys are channel names and values are ciphers (possibly empty)
+     *  \note This method is threadsafe
+     *
+     *  \param user       The id of the networks owner
+     *  \param networkId  The Id of the network
+     */
+    static inline QHash<QString, QByteArray> bufferCiphers(UserId user, const NetworkId &networkId)
+    {
+        return instance()->_storage->bufferCiphers(user, networkId);
+    }
+
+
+    //! Update the cipher of a buffer
+    /** \note This method is threadsafe
+     *
+     *  \param user        The Id of the networks owner
+     *  \param networkId   The Id of the network
+     *  \param bufferName The Cname of the buffer
+     *  \param cipher      The cipher for the buffer
+     */
+    static inline void setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher)
+    {
+        return instance()->_storage->setBufferCipher(user, networkId, bufferName, cipher);
+    }
+
+
     //! Update the key of a channel
     /** \note This method is threadsafe
      *
index c89e932..4dd946e 100644 (file)
  ***************************************************************************/
 
 #include "coreircuser.h"
+#include "corenetwork.h"
 
 CoreIrcUser::CoreIrcUser(const QString &hostmask, Network *network) : IrcUser(hostmask, network)
 {
 #ifdef HAVE_QCA2
     _cipher = 0;
+
+    // Get the cipher key from CoreNetwork if present
+    CoreNetwork *coreNetwork = qobject_cast<CoreNetwork *>(network);
+    if (coreNetwork) {
+        QByteArray key = coreNetwork->readChannelCipherKey(nick().toLower());
+        if (!key.isEmpty()) {
+            if (!_cipher) {
+                _cipher = new Cipher();
+            }
+            setEncrypted(_cipher->setKey(key));
+        }
+    }
 #endif
 }
 
@@ -31,6 +44,15 @@ CoreIrcUser::CoreIrcUser(const QString &hostmask, Network *network) : IrcUser(ho
 CoreIrcUser::~CoreIrcUser()
 {
 #ifdef HAVE_QCA2
+    // Store the cipher key in CoreNetwork, including empty keys if a cipher
+    // exists. There is no need to store the empty key if no cipher exists; no
+    // key was present when instantiating and no key was set during the
+    // channel's lifetime.
+    CoreNetwork *coreNetwork = qobject_cast<CoreNetwork *>(network());
+    if (coreNetwork && _cipher) {
+        coreNetwork->storeChannelCipherKey(nick().toLower(), _cipher->key());
+    }
+
     delete _cipher;
 #endif
 }
index acebcee..30f319b 100644 (file)
@@ -63,6 +63,11 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session)
         _channelKeys[chan.toLower()] = channels[chan];
     }
 
+    QHash<QString, QByteArray> bufferCiphers = coreSession()->bufferCiphers(networkId());
+    foreach(QString buffer, bufferCiphers.keys()) {
+        storeChannelCipherKey(buffer.toLower(), bufferCiphers[buffer]);
+    }
+
     connect(networkConfig(), SIGNAL(pingTimeoutEnabledSet(bool)), SLOT(enablePingTimeout(bool)));
     connect(networkConfig(), SIGNAL(pingIntervalSet(int)), SLOT(setPingInterval(int)));
     connect(networkConfig(), SIGNAL(autoWhoEnabledSet(bool)), SLOT(setAutoWhoEnabled(bool)));
@@ -442,6 +447,7 @@ void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
     CoreIrcChannel *c = qobject_cast<CoreIrcChannel*>(ircChannel(target));
     if (c) {
         c->setEncrypted(c->cipher()->setKey(key));
+        coreSession()->setBufferCipher(networkId(), target, key);
         return;
     }
 
@@ -451,6 +457,7 @@ void CoreNetwork::setCipherKey(const QString &target, const QByteArray &key)
 
     if (u) {
         u->setEncrypted(u->cipher()->setKey(key));
+        coreSession()->setBufferCipher(networkId(), target, key);
         return;
     }
 }
index 3cc3d07..249690c 100644 (file)
@@ -288,6 +288,17 @@ QHash<QString, QString> CoreSession::persistentChannels(NetworkId id) const
 }
 
 
+QHash<QString, QByteArray> CoreSession::bufferCiphers(NetworkId id) const
+{
+    return Core::bufferCiphers(user(), id);
+}
+
+void CoreSession::setBufferCipher(NetworkId id, const QString &bufferName, const QByteArray &cipher) const
+{
+    Core::setBufferCipher(user(), id, bufferName, cipher);
+}
+
+
 // FIXME switch to BufferId
 void CoreSession::msgFromClient(BufferInfo bufinfo, QString msg)
 {
index fb5995b..9967f80 100644 (file)
@@ -138,6 +138,9 @@ public slots:
 
     QHash<QString, QString> persistentChannels(NetworkId) const;
 
+    QHash<QString, QByteArray> bufferCiphers(NetworkId id) const;
+    void setBufferCipher(NetworkId id, const QString &bufferName, const QByteArray &cipher) const;
+
     /**
      * Marks us away (or unaway) on all networks
      *
index af04a59..b64592d 100644 (file)
@@ -1440,6 +1440,44 @@ Message::Types PostgreSqlStorage::bufferActivity(BufferId bufferId, MsgId lastSe
     return result;
 }
 
+QHash<QString, QByteArray> PostgreSqlStorage::bufferCiphers(UserId user, const NetworkId &networkId)
+{
+    QHash<QString, QByteArray> bufferCiphers;
+
+    QSqlDatabase db = logDb();
+    if (!beginReadOnlyTransaction(db)) {
+        qWarning() << "PostgreSqlStorage::persistentChannels(): cannot start read only transaction!";
+        qWarning() << " -" << qPrintable(db.lastError().text());
+        return bufferCiphers;
+    }
+
+    QSqlQuery query(db);
+    query.prepare(queryString("select_buffer_ciphers"));
+    query.bindValue(":userid", user.toInt());
+    query.bindValue(":networkid", networkId.toInt());
+    safeExec(query);
+    watchQuery(query);
+
+    while (query.next()) {
+        bufferCiphers[query.value(0).toString()] = QByteArray::fromHex(query.value(1).toString().toUtf8());
+    }
+
+    db.commit();
+    return bufferCiphers;
+}
+
+void PostgreSqlStorage::setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher)
+{
+    QSqlQuery query(logDb());
+    query.prepare(queryString("update_buffer_cipher"));
+    query.bindValue(":userid", user.toInt());
+    query.bindValue(":networkid", networkId.toInt());
+    query.bindValue(":buffercname", bufferName.toLower());
+    query.bindValue(":cipher", QString(cipher.toHex()));
+    safeExec(query);
+    watchQuery(query);
+}
+
 bool PostgreSqlStorage::logMessage(Message &msg)
 {
     QSqlDatabase db = logDb();
@@ -2066,6 +2104,7 @@ bool PostgreSqlMigrationWriter::writeMo(const BufferMO &buffer)
     bindValue(10, buffer.bufferactivity);
     bindValue(11, buffer.key);
     bindValue(12, buffer.joined);
+    bindValue(13, buffer.cipher);
     return exec();
 }
 
index 81439bb..3146581 100644 (file)
@@ -98,6 +98,8 @@ public slots:
     void setBufferActivity(UserId id, BufferId bufferId, Message::Types type) override;
     QHash<BufferId, Message::Types> bufferActivities(UserId id) override;
     Message::Types bufferActivity(BufferId bufferId, MsgId lastSeenMsgId) override;
+    QHash<QString, QByteArray> bufferCiphers(UserId user, const NetworkId &networkId) override;
+    void setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher) override;
 
     /* Message handling */
     bool logMessage(Message &msg) override;
index edaaa32..60ef637 100644 (file)
@@ -39,6 +39,7 @@
     <file>./SQL/PostgreSQL/select_buffer_bufferactivities.sql</file>
     <file>./SQL/PostgreSQL/select_buffer_bufferactivity.sql</file>
     <file>./SQL/PostgreSQL/select_buffer_by_id.sql</file>
+    <file>./SQL/PostgreSQL/select_buffer_ciphers.sql</file>
     <file>./SQL/PostgreSQL/select_buffer_lastseen_messages.sql</file>
     <file>./SQL/PostgreSQL/select_buffer_markerlinemsgids.sql</file>
     <file>./SQL/PostgreSQL/select_buffers.sql</file>
@@ -80,6 +81,7 @@
     <file>./SQL/PostgreSQL/setup_130_function_lastmsgid.sql</file>
     <file>./SQL/PostgreSQL/update_backlog_bufferid.sql</file>
     <file>./SQL/PostgreSQL/update_buffer_bufferactivity.sql</file>
+    <file>./SQL/PostgreSQL/update_buffer_cipher.sql</file>
     <file>./SQL/PostgreSQL/update_buffer_lastseen.sql</file>
     <file>./SQL/PostgreSQL/update_buffer_markerlinemsgid.sql</file>
     <file>./SQL/PostgreSQL/update_buffer_name.sql</file>
     <file>./SQL/PostgreSQL/version/22/upgrade_000_alter_quasseluser_add_authenticator.sql</file>
     <file>./SQL/PostgreSQL/version/23/upgrade_000_create_senderprefixes.sql</file>
     <file>./SQL/PostgreSQL/version/24/upgrade_000_alter_buffer_add_bufferactivity.sql</file>
+    <file>./SQL/PostgreSQL/version/25/upgrade_000_alter_buffer_add_cipher.sql</file>
     <file>./SQL/SQLite/delete_backlog_by_uid.sql</file>
     <file>./SQL/SQLite/delete_backlog_for_buffer.sql</file>
     <file>./SQL/SQLite/delete_backlog_for_network.sql</file>
     <file>./SQL/SQLite/select_buffer_bufferactivities.sql</file>
     <file>./SQL/SQLite/select_buffer_bufferactivity.sql</file>
     <file>./SQL/SQLite/select_buffer_by_id.sql</file>
+    <file>./SQL/SQLite/select_buffer_ciphers.sql</file>
     <file>./SQL/SQLite/select_buffer_lastseen_messages.sql</file>
     <file>./SQL/SQLite/select_buffer_markerlinemsgids.sql</file>
     <file>./SQL/SQLite/select_buffers.sql</file>
     <file>./SQL/SQLite/setup_140_identity_nick.sql</file>
     <file>./SQL/SQLite/update_backlog_bufferid.sql</file>
     <file>./SQL/SQLite/update_buffer_bufferactivity.sql</file>
+    <file>./SQL/SQLite/update_buffer_cipher.sql</file>
     <file>./SQL/SQLite/update_buffer_lastseen.sql</file>
     <file>./SQL/SQLite/update_buffer_markerlinemsgid.sql</file>
     <file>./SQL/SQLite/update_buffer_name.sql</file>
     <file>./SQL/SQLite/version/24/upgrade_000_create_senderprefixes.sql</file>
     <file>./SQL/SQLite/version/25/upgrade_000_alter_buffer_add_bufferactivity.sql</file>
     <file>./SQL/SQLite/version/26/upgrade_000_create_buffer_idx.sql</file>
+    <file>./SQL/SQLite/version/27/upgrade_000_alter_buffer_add_cipher.sql</file>
 </qresource>
 </RCC>
index a6d7671..6333882 100644 (file)
@@ -1576,6 +1576,50 @@ Message::Types SqliteStorage::bufferActivity(BufferId bufferId, MsgId lastSeenMs
     return result;
 }
 
+QHash<QString, QByteArray> SqliteStorage::bufferCiphers(UserId user, const NetworkId &networkId)
+{
+    QHash<QString, QByteArray> bufferCiphers;
+
+    QSqlDatabase db = logDb();
+    db.transaction();
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("select_buffer_ciphers"));
+        query.bindValue(":userid", user.toInt());
+        query.bindValue(":networkid", networkId.toInt());
+
+        lockForRead();
+        safeExec(query);
+        watchQuery(query);
+        while (query.next()) {
+            bufferCiphers[query.value(0).toString()] = QByteArray::fromHex(query.value(1).toString().toUtf8());
+        }
+    }
+    unlock();
+    return bufferCiphers;
+}
+
+void SqliteStorage::setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher)
+{
+    QSqlDatabase db = logDb();
+    db.transaction();
+
+    {
+        QSqlQuery query(db);
+        query.prepare(queryString("update_buffer_cipher"));
+        query.bindValue(":userid", user.toInt());
+        query.bindValue(":networkid", networkId.toInt());
+        query.bindValue(":buffercname", bufferName.toLower());
+        query.bindValue(":cipher", QString(cipher.toHex()));
+
+        lockForWrite();
+        safeExec(query);
+        watchQuery(query);
+        db.commit();
+    }
+    unlock();
+}
+
 bool SqliteStorage::logMessage(Message &msg)
 {
     QSqlDatabase db = logDb();
@@ -2074,6 +2118,7 @@ bool SqliteMigrationReader::readMo(BufferMO &buffer)
     buffer.bufferactivity = value(10).toInt();
     buffer.key = value(11).toString();
     buffer.joined = value(12).toInt() == 1 ? true : false;
+    buffer.cipher = value(13).toString();
     return true;
 }
 
index aaf2d69..aa0dad1 100644 (file)
@@ -99,6 +99,8 @@ public slots:
     void setBufferActivity(UserId id, BufferId bufferId, Message::Types type) override;
     QHash<BufferId, Message::Types> bufferActivities(UserId id) override;
     Message::Types bufferActivity(BufferId bufferId, MsgId lastSeenMsgId) override;
+    QHash<QString, QByteArray> bufferCiphers(UserId user, const NetworkId &networkId) override;
+    void setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher) override;
 
     /* Message handling */
     bool logMessage(Message &msg) override;
index b915d04..49f2df8 100644 (file)
@@ -411,6 +411,25 @@ public slots:
      */
     virtual Message::Types bufferActivity(BufferId bufferId, MsgId lastSeenMsgId) = 0;
 
+    //! Get a hash of buffers with their ciphers for a given network
+    /** The keys are channel names and values are ciphers (possibly empty)
+     *  \note This method is threadsafe
+     *
+     *  \param user       The id of the networks owner
+     *  \param networkId  The Id of the network
+     */
+    virtual QHash<QString, QByteArray> bufferCiphers(UserId user, const NetworkId &networkId) = 0;
+
+    //! Update the cipher of a buffer
+    /** \note This method is threadsafe
+     *
+     *  \param user        The Id of the networks owner
+     *  \param networkId   The Id of the network
+     *  \param bufferName The Cname of the buffer
+     *  \param cipher      The cipher for the buffer
+     */
+    virtual void setBufferCipher(UserId user, const NetworkId &networkId, const QString &bufferName, const QByteArray &cipher) = 0;
+
     /* Message handling */
 
     //! Store a Message in the storage backend and set its unique Id.