From c382e0c11f80fb37307ecc42c487aa433c97ad8c Mon Sep 17 00:00:00 2001 From: Michael Marley Date: Tue, 8 May 2018 13:49:33 -0500 Subject: [PATCH] Persist Blowfish keys in the database 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. --- .../SQL/PostgreSQL/migrate_write_buffer.sql | 4 +- .../SQL/PostgreSQL/select_buffer_ciphers.sql | 3 ++ src/core/SQL/PostgreSQL/setup_050_buffer.sql | 1 + .../SQL/PostgreSQL/update_buffer_cipher.sql | 3 ++ .../upgrade_000_alter_buffer_add_cipher.sql | 2 + src/core/SQL/SQLite/migrate_read_buffer.sql | 2 +- src/core/SQL/SQLite/select_buffer_ciphers.sql | 3 ++ src/core/SQL/SQLite/setup_030_buffer.sql | 1 + src/core/SQL/SQLite/update_buffer_cipher.sql | 3 ++ .../upgrade_000_alter_buffer_add_cipher.sql | 2 + src/core/abstractsqlstorage.h | 1 + src/core/core.h | 27 +++++++++++ src/core/coreircuser.cpp | 22 +++++++++ src/core/corenetwork.cpp | 7 +++ src/core/coresession.cpp | 11 +++++ src/core/coresession.h | 3 ++ src/core/postgresqlstorage.cpp | 39 ++++++++++++++++ src/core/postgresqlstorage.h | 2 + src/core/sql.qrc | 6 +++ src/core/sqlitestorage.cpp | 45 +++++++++++++++++++ src/core/sqlitestorage.h | 2 + src/core/storage.h | 19 ++++++++ 22 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/core/SQL/PostgreSQL/select_buffer_ciphers.sql create mode 100644 src/core/SQL/PostgreSQL/update_buffer_cipher.sql create mode 100644 src/core/SQL/PostgreSQL/version/25/upgrade_000_alter_buffer_add_cipher.sql create mode 100644 src/core/SQL/SQLite/select_buffer_ciphers.sql create mode 100644 src/core/SQL/SQLite/update_buffer_cipher.sql create mode 100644 src/core/SQL/SQLite/version/27/upgrade_000_alter_buffer_add_cipher.sql diff --git a/src/core/SQL/PostgreSQL/migrate_write_buffer.sql b/src/core/SQL/PostgreSQL/migrate_write_buffer.sql index ba80880e..3ae6cf47 100644 --- a/src/core/SQL/PostgreSQL/migrate_write_buffer.sql +++ b/src/core/SQL/PostgreSQL/migrate_write_buffer.sql @@ -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 index 00000000..45120d63 --- /dev/null +++ b/src/core/SQL/PostgreSQL/select_buffer_ciphers.sql @@ -0,0 +1,3 @@ +SELECT buffername, cipher +FROM buffer +WHERE userid = :userid AND networkid = :networkid AND buffertype IN (2, 4) diff --git a/src/core/SQL/PostgreSQL/setup_050_buffer.sql b/src/core/SQL/PostgreSQL/setup_050_buffer.sql index a10265b5..0d92f43c 100644 --- a/src/core/SQL/PostgreSQL/setup_050_buffer.sql +++ b/src/core/SQL/PostgreSQL/setup_050_buffer.sql @@ -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 index 00000000..f2017c47 --- /dev/null +++ b/src/core/SQL/PostgreSQL/update_buffer_cipher.sql @@ -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 index 00000000..15b2937f --- /dev/null +++ b/src/core/SQL/PostgreSQL/version/25/upgrade_000_alter_buffer_add_cipher.sql @@ -0,0 +1,2 @@ +ALTER TABLE buffer +ADD COLUMN cipher TEXT diff --git a/src/core/SQL/SQLite/migrate_read_buffer.sql b/src/core/SQL/SQLite/migrate_read_buffer.sql index 5d200375..da20e39c 100644 --- a/src/core/SQL/SQLite/migrate_read_buffer.sql +++ b/src/core/SQL/SQLite/migrate_read_buffer.sql @@ -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 index 00000000..45120d63 --- /dev/null +++ b/src/core/SQL/SQLite/select_buffer_ciphers.sql @@ -0,0 +1,3 @@ +SELECT buffername, cipher +FROM buffer +WHERE userid = :userid AND networkid = :networkid AND buffertype IN (2, 4) diff --git a/src/core/SQL/SQLite/setup_030_buffer.sql b/src/core/SQL/SQLite/setup_030_buffer.sql index 3e09d061..5781c24d 100644 --- a/src/core/SQL/SQLite/setup_030_buffer.sql +++ b/src/core/SQL/SQLite/setup_030_buffer.sql @@ -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 index 00000000..f2017c47 --- /dev/null +++ b/src/core/SQL/SQLite/update_buffer_cipher.sql @@ -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 index 00000000..15b2937f --- /dev/null +++ b/src/core/SQL/SQLite/version/27/upgrade_000_alter_buffer_add_cipher.sql @@ -0,0 +1,2 @@ +ALTER TABLE buffer +ADD COLUMN cipher TEXT diff --git a/src/core/abstractsqlstorage.h b/src/core/abstractsqlstorage.h index 9bcd2868..c554d9aa 100644 --- a/src/core/abstractsqlstorage.h +++ b/src/core/abstractsqlstorage.h @@ -236,6 +236,7 @@ public: int bufferactivity; QString key; bool joined; + QString cipher; }; struct BacklogMO { diff --git a/src/core/core.h b/src/core/core.h index 5f909175..98e2612e 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -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 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 * diff --git a/src/core/coreircuser.cpp b/src/core/coreircuser.cpp index c89e9326..4dd946ea 100644 --- a/src/core/coreircuser.cpp +++ b/src/core/coreircuser.cpp @@ -19,11 +19,24 @@ ***************************************************************************/ #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(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(network()); + if (coreNetwork && _cipher) { + coreNetwork->storeChannelCipherKey(nick().toLower(), _cipher->key()); + } + delete _cipher; #endif } diff --git a/src/core/corenetwork.cpp b/src/core/corenetwork.cpp index acebcee6..30f319ba 100644 --- a/src/core/corenetwork.cpp +++ b/src/core/corenetwork.cpp @@ -63,6 +63,11 @@ CoreNetwork::CoreNetwork(const NetworkId &networkid, CoreSession *session) _channelKeys[chan.toLower()] = channels[chan]; } + QHash 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(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; } } diff --git a/src/core/coresession.cpp b/src/core/coresession.cpp index 3cc3d07a..249690c4 100644 --- a/src/core/coresession.cpp +++ b/src/core/coresession.cpp @@ -288,6 +288,17 @@ QHash CoreSession::persistentChannels(NetworkId id) const } +QHash 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) { diff --git a/src/core/coresession.h b/src/core/coresession.h index fb5995bb..9967f802 100644 --- a/src/core/coresession.h +++ b/src/core/coresession.h @@ -138,6 +138,9 @@ public slots: QHash persistentChannels(NetworkId) const; + QHash bufferCiphers(NetworkId id) const; + void setBufferCipher(NetworkId id, const QString &bufferName, const QByteArray &cipher) const; + /** * Marks us away (or unaway) on all networks * diff --git a/src/core/postgresqlstorage.cpp b/src/core/postgresqlstorage.cpp index af04a595..b64592d7 100644 --- a/src/core/postgresqlstorage.cpp +++ b/src/core/postgresqlstorage.cpp @@ -1440,6 +1440,44 @@ Message::Types PostgreSqlStorage::bufferActivity(BufferId bufferId, MsgId lastSe return result; } +QHash PostgreSqlStorage::bufferCiphers(UserId user, const NetworkId &networkId) +{ + QHash 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(); } diff --git a/src/core/postgresqlstorage.h b/src/core/postgresqlstorage.h index 81439bbb..31465817 100644 --- a/src/core/postgresqlstorage.h +++ b/src/core/postgresqlstorage.h @@ -98,6 +98,8 @@ public slots: void setBufferActivity(UserId id, BufferId bufferId, Message::Types type) override; QHash bufferActivities(UserId id) override; Message::Types bufferActivity(BufferId bufferId, MsgId lastSeenMsgId) override; + QHash 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; diff --git a/src/core/sql.qrc b/src/core/sql.qrc index edaaa326..60ef6373 100644 --- a/src/core/sql.qrc +++ b/src/core/sql.qrc @@ -39,6 +39,7 @@ ./SQL/PostgreSQL/select_buffer_bufferactivities.sql ./SQL/PostgreSQL/select_buffer_bufferactivity.sql ./SQL/PostgreSQL/select_buffer_by_id.sql + ./SQL/PostgreSQL/select_buffer_ciphers.sql ./SQL/PostgreSQL/select_buffer_lastseen_messages.sql ./SQL/PostgreSQL/select_buffer_markerlinemsgids.sql ./SQL/PostgreSQL/select_buffers.sql @@ -80,6 +81,7 @@ ./SQL/PostgreSQL/setup_130_function_lastmsgid.sql ./SQL/PostgreSQL/update_backlog_bufferid.sql ./SQL/PostgreSQL/update_buffer_bufferactivity.sql + ./SQL/PostgreSQL/update_buffer_cipher.sql ./SQL/PostgreSQL/update_buffer_lastseen.sql ./SQL/PostgreSQL/update_buffer_markerlinemsgid.sql ./SQL/PostgreSQL/update_buffer_name.sql @@ -111,6 +113,7 @@ ./SQL/PostgreSQL/version/22/upgrade_000_alter_quasseluser_add_authenticator.sql ./SQL/PostgreSQL/version/23/upgrade_000_create_senderprefixes.sql ./SQL/PostgreSQL/version/24/upgrade_000_alter_buffer_add_bufferactivity.sql + ./SQL/PostgreSQL/version/25/upgrade_000_alter_buffer_add_cipher.sql ./SQL/SQLite/delete_backlog_by_uid.sql ./SQL/SQLite/delete_backlog_for_buffer.sql ./SQL/SQLite/delete_backlog_for_network.sql @@ -150,6 +153,7 @@ ./SQL/SQLite/select_buffer_bufferactivities.sql ./SQL/SQLite/select_buffer_bufferactivity.sql ./SQL/SQLite/select_buffer_by_id.sql + ./SQL/SQLite/select_buffer_ciphers.sql ./SQL/SQLite/select_buffer_lastseen_messages.sql ./SQL/SQLite/select_buffer_markerlinemsgids.sql ./SQL/SQLite/select_buffers.sql @@ -193,6 +197,7 @@ ./SQL/SQLite/setup_140_identity_nick.sql ./SQL/SQLite/update_backlog_bufferid.sql ./SQL/SQLite/update_buffer_bufferactivity.sql + ./SQL/SQLite/update_buffer_cipher.sql ./SQL/SQLite/update_buffer_lastseen.sql ./SQL/SQLite/update_buffer_markerlinemsgid.sql ./SQL/SQLite/update_buffer_name.sql @@ -297,5 +302,6 @@ ./SQL/SQLite/version/24/upgrade_000_create_senderprefixes.sql ./SQL/SQLite/version/25/upgrade_000_alter_buffer_add_bufferactivity.sql ./SQL/SQLite/version/26/upgrade_000_create_buffer_idx.sql + ./SQL/SQLite/version/27/upgrade_000_alter_buffer_add_cipher.sql diff --git a/src/core/sqlitestorage.cpp b/src/core/sqlitestorage.cpp index a6d7671f..63338824 100644 --- a/src/core/sqlitestorage.cpp +++ b/src/core/sqlitestorage.cpp @@ -1576,6 +1576,50 @@ Message::Types SqliteStorage::bufferActivity(BufferId bufferId, MsgId lastSeenMs return result; } +QHash SqliteStorage::bufferCiphers(UserId user, const NetworkId &networkId) +{ + QHash 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; } diff --git a/src/core/sqlitestorage.h b/src/core/sqlitestorage.h index aaf2d69a..aa0dad12 100644 --- a/src/core/sqlitestorage.h +++ b/src/core/sqlitestorage.h @@ -99,6 +99,8 @@ public slots: void setBufferActivity(UserId id, BufferId bufferId, Message::Types type) override; QHash bufferActivities(UserId id) override; Message::Types bufferActivity(BufferId bufferId, MsgId lastSeenMsgId) override; + QHash 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; diff --git a/src/core/storage.h b/src/core/storage.h index b915d047..49f2df84 100644 --- a/src/core/storage.h +++ b/src/core/storage.h @@ -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 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. -- 2.20.1