diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/connection.cpp | 14 | ||||
-rw-r--r-- | lib/connection.h | 8 | ||||
-rw-r--r-- | lib/database.cpp | 21 | ||||
-rw-r--r-- | lib/database.h | 42 | ||||
-rw-r--r-- | lib/e2ee/qolmoutboundsession.cpp | 4 | ||||
-rw-r--r-- | lib/e2ee/qolmoutboundsession.h | 4 | ||||
-rw-r--r-- | lib/room.cpp | 156 |
7 files changed, 151 insertions, 98 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index 8fd2d6cf..1193eb75 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -2187,7 +2187,7 @@ QJsonObject Connection::decryptNotification(const QJsonObject ¬ification) return decrypted ? decrypted->fullJson() : QJsonObject(); } -Database* Connection::database() +Database* Connection::database() const { return d->database; } @@ -2271,14 +2271,18 @@ void Connection::createOlmSession(const QString& theirIdentityKey, d->olmSessions[theirIdentityKey].push_back(std::move(*session)); } -QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession(Room* room) +QOlmOutboundGroupSessionPtr Connection::loadCurrentOutboundMegolmSession( + const QString& roomId) const { - return d->database->loadCurrentOutboundMegolmSession(room->id(), d->picklingMode); + return d->database->loadCurrentOutboundMegolmSession(roomId, + d->picklingMode); } -void Connection::saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data) +void Connection::saveCurrentOutboundMegolmSession( + const QString& roomId, const QOlmOutboundGroupSession& session) const { - d->database->saveCurrentOutboundMegolmSession(room->id(), d->picklingMode, data); + d->database->saveCurrentOutboundMegolmSession(roomId, d->picklingMode, + session); } #endif diff --git a/lib/connection.h b/lib/connection.h index 43e285c1..a2824744 100644 --- a/lib/connection.h +++ b/lib/connection.h @@ -318,16 +318,16 @@ public: bool isLoggedIn() const; #ifdef Quotient_E2EE_ENABLED QOlmAccount* olmAccount() const; - Database* database(); + Database* database() const; UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadRoomMegolmSessions( const Room* room); void saveMegolmSession(const Room* room, const QOlmInboundGroupSession& session); bool hasOlmSession(const QString& user, const QString& deviceId) const; - QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(Room* room); - void saveCurrentOutboundMegolmSession(Room *room, const QOlmOutboundGroupSessionPtr& data); - + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession( + const QString& roomId) const; + void saveCurrentOutboundMegolmSession(const QString& roomId, const QOlmOutboundGroupSession &session) const; //This assumes that an olm session with (user, device) exists std::pair<QOlmMessage::Type, QByteArray> olmEncryptMessage( diff --git a/lib/database.cpp b/lib/database.cpp index 0119b35c..193ff54e 100644 --- a/lib/database.cpp +++ b/lib/database.cpp @@ -307,20 +307,22 @@ void Database::setOlmSessionLastReceived(const QString& sessionId, const QDateTi commit(); } -void Database::saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& session) +void Database::saveCurrentOutboundMegolmSession( + const QString& roomId, const PicklingMode& picklingMode, + const QOlmOutboundGroupSession& session) { - const auto pickle = session->pickle(picklingMode); + const auto pickle = session.pickle(picklingMode); if (pickle) { auto deleteQuery = prepareQuery(QStringLiteral("DELETE FROM outbound_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId;")); deleteQuery.bindValue(":roomId", roomId); - deleteQuery.bindValue(":sessionId", session->sessionId()); + deleteQuery.bindValue(":sessionId", session.sessionId()); auto insertQuery = prepareQuery(QStringLiteral("INSERT INTO outbound_megolm_sessions(roomId, sessionId, pickle, creationTime, messageCount) VALUES(:roomId, :sessionId, :pickle, :creationTime, :messageCount);")); insertQuery.bindValue(":roomId", roomId); - insertQuery.bindValue(":sessionId", session->sessionId()); + insertQuery.bindValue(":sessionId", session.sessionId()); insertQuery.bindValue(":pickle", pickle.value()); - insertQuery.bindValue(":creationTime", session->creationTime()); - insertQuery.bindValue(":messageCount", session->messageCount()); + insertQuery.bindValue(":creationTime", session.creationTime()); + insertQuery.bindValue(":messageCount", session.messageCount()); transaction(); execute(deleteQuery); @@ -362,7 +364,9 @@ void Database::setDevicesReceivedKey(const QString& roomId, const QVector<std::t commit(); } -QHash<QString, QStringList> Database::devicesWithoutKey(const QString& roomId, QHash<QString, QStringList>& devices, const QString &sessionId) +QMultiHash<QString, QString> Database::devicesWithoutKey( + const QString& roomId, QMultiHash<QString, QString> devices, + const QString& sessionId) { auto query = prepareQuery(QStringLiteral("SELECT userId, deviceId FROM sent_megolm_sessions WHERE roomId=:roomId AND sessionId=:sessionId")); query.bindValue(":roomId", roomId); @@ -371,7 +375,8 @@ QHash<QString, QStringList> Database::devicesWithoutKey(const QString& roomId, Q execute(query); commit(); while (query.next()) { - devices[query.value("userId").toString()].removeAll(query.value("deviceId").toString()); + devices.remove(query.value("userId").toString(), + query.value("deviceId").toString()); } return devices; } diff --git a/lib/database.h b/lib/database.h index 45348c8d..4091d61b 100644 --- a/lib/database.h +++ b/lib/database.h @@ -32,22 +32,40 @@ public: QByteArray accountPickle(); void setAccountPickle(const QByteArray &pickle); void clear(); - void saveOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle, const QDateTime& timestamp); - UnorderedMap<QString, std::vector<QOlmSessionPtr>> loadOlmSessions(const PicklingMode& picklingMode); - UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadMegolmSessions(const QString& roomId, const PicklingMode& picklingMode); - void saveMegolmSession(const QString& roomId, const QString& sessionId, const QByteArray& pickle, const QString& senderId, const QString& olmSessionId); - void addGroupSessionIndexRecord(const QString& roomId, const QString& sessionId, uint32_t index, const QString& eventId, qint64 ts); - std::pair<QString, qint64> groupSessionIndexRecord(const QString& roomId, const QString& sessionId, qint64 index); + void saveOlmSession(const QString& senderKey, const QString& sessionId, + const QByteArray& pickle, const QDateTime& timestamp); + UnorderedMap<QString, std::vector<QOlmSessionPtr>> loadOlmSessions( + const PicklingMode& picklingMode); + UnorderedMap<QString, QOlmInboundGroupSessionPtr> loadMegolmSessions( + const QString& roomId, const PicklingMode& picklingMode); + void saveMegolmSession(const QString& roomId, const QString& sessionId, + const QByteArray& pickle, const QString& senderId, + const QString& olmSessionId); + void addGroupSessionIndexRecord(const QString& roomId, + const QString& sessionId, uint32_t index, + const QString& eventId, qint64 ts); + std::pair<QString, qint64> groupSessionIndexRecord(const QString& roomId, + const QString& sessionId, + qint64 index); void clearRoomData(const QString& roomId); - void setOlmSessionLastReceived(const QString& sessionId, const QDateTime& timestamp); - QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode); - void saveCurrentOutboundMegolmSession(const QString& roomId, const PicklingMode& picklingMode, const QOlmOutboundGroupSessionPtr& data); - void updateOlmSession(const QString& senderKey, const QString& sessionId, const QByteArray& pickle); + void setOlmSessionLastReceived(const QString& sessionId, + const QDateTime& timestamp); + QOlmOutboundGroupSessionPtr loadCurrentOutboundMegolmSession( + const QString& roomId, const PicklingMode& picklingMode); + void saveCurrentOutboundMegolmSession( + const QString& roomId, const PicklingMode& picklingMode, + const QOlmOutboundGroupSession& session); + void updateOlmSession(const QString& senderKey, const QString& sessionId, + const QByteArray& pickle); // Returns a map UserId -> [DeviceId] that have not received key yet - QHash<QString, QStringList> devicesWithoutKey(const QString& roomId, QHash<QString, QStringList>& devices, const QString &sessionId); + QMultiHash<QString, QString> devicesWithoutKey(const QString& roomId, QMultiHash<QString, QString> devices, + const QString& sessionId); // 'devices' contains tuples {userId, deviceId, curveKey} - void setDevicesReceivedKey(const QString& roomId, const QVector<std::tuple<QString, QString, QString>>& devices, const QString& sessionId, int index); + void setDevicesReceivedKey( + const QString& roomId, + const QVector<std::tuple<QString, QString, QString>>& devices, + const QString& sessionId, int index); private: void migrateTo1(); diff --git a/lib/e2ee/qolmoutboundsession.cpp b/lib/e2ee/qolmoutboundsession.cpp index 76188d08..a2eff2c8 100644 --- a/lib/e2ee/qolmoutboundsession.cpp +++ b/lib/e2ee/qolmoutboundsession.cpp @@ -44,7 +44,7 @@ QOlmOutboundGroupSessionPtr QOlmOutboundGroupSession::create() return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession); } -QOlmExpected<QByteArray> QOlmOutboundGroupSession::pickle(const PicklingMode &mode) +QOlmExpected<QByteArray> QOlmOutboundGroupSession::pickle(const PicklingMode &mode) const { QByteArray pickledBuf(olm_pickle_outbound_group_session_length(m_groupSession), '0'); QByteArray key = toKey(mode); @@ -79,7 +79,7 @@ QOlmExpected<QOlmOutboundGroupSessionPtr> QOlmOutboundGroupSession::unpickle(con return std::make_unique<QOlmOutboundGroupSession>(olmOutboundGroupSession); } -QOlmExpected<QByteArray> QOlmOutboundGroupSession::encrypt(const QString &plaintext) +QOlmExpected<QByteArray> QOlmOutboundGroupSession::encrypt(const QString &plaintext) const { QByteArray plaintextBuf = plaintext.toUtf8(); const auto messageMaxLength = olm_group_encrypt_message_length(m_groupSession, plaintextBuf.length()); diff --git a/lib/e2ee/qolmoutboundsession.h b/lib/e2ee/qolmoutboundsession.h index c20613d3..9a82d22a 100644 --- a/lib/e2ee/qolmoutboundsession.h +++ b/lib/e2ee/qolmoutboundsession.h @@ -21,14 +21,14 @@ public: //! Throw OlmError on errors static QOlmOutboundGroupSessionPtr create(); //! Serialises a `QOlmOutboundGroupSession` to encrypted Base64. - QOlmExpected<QByteArray> pickle(const PicklingMode &mode); + QOlmExpected<QByteArray> pickle(const PicklingMode &mode) const; //! Deserialises from encrypted Base64 that was previously obtained by //! pickling a `QOlmOutboundGroupSession`. static QOlmExpected<QOlmOutboundGroupSessionPtr> unpickle( const QByteArray& pickled, const PicklingMode& mode); //! Encrypts a plaintext message using the session. - QOlmExpected<QByteArray> encrypt(const QString& plaintext); + QOlmExpected<QByteArray> encrypt(const QString& plaintext) const; //! Get the current message index for this session. //! diff --git a/lib/room.cpp b/lib/room.cpp index 26fe80e3..07d03467 100644 --- a/lib/room.cpp +++ b/lib/room.cpp @@ -443,9 +443,11 @@ public: return q->getCurrentState<EncryptionEvent>()->rotationPeriodMsgs(); } void createMegolmSession() { - qCDebug(E2EE) << "Creating new outbound megolm session for room " << q->id(); + qCDebug(E2EE) << "Creating new outbound megolm session for room " + << q->objectName(); currentOutboundMegolmSession = QOlmOutboundGroupSession::create(); - connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); + connection->saveCurrentOutboundMegolmSession( + id, *currentOutboundMegolmSession); const auto sessionKey = currentOutboundMegolmSession->sessionKey(); if(!sessionKey) { @@ -477,90 +479,113 @@ public: return makeEvent<EncryptedEvent>(encrypted, connection->olmAccount()->identityKeys().curve25519); } - QHash<QString, QStringList> getDevicesWithoutKey() const + QMultiHash<QString, QString> getDevicesWithoutKey() const { - QHash<QString, QStringList> devices; - for (const auto& user : q->users()) { - devices[user->id()] = q->connection()->devicesForUser(user->id()); + QMultiHash<QString, QString> devices; + for (const auto& user : q->users()) + for (const auto& deviceId : connection->devicesForUser(user->id())) + devices.insert(user->id(), deviceId); + + return connection->database()->devicesWithoutKey( + id, devices, currentOutboundMegolmSession->sessionId()); + } + + bool createOlmSession(const QString& user, const QString& device, + const QJsonObject& oneTimeKeyObject) const + { + static QOlmUtility verifier; + qDebug(E2EE) << "Creating a new session for" << user << device; + if (oneTimeKeyObject.isEmpty()) { + qWarning(E2EE) << "No one time key for" << user << device; + return false; } - return q->connection()->database()->devicesWithoutKey(q->id(), devices, QString(currentOutboundMegolmSession->sessionId())); + const auto oneTimeKeyForId = *oneTimeKeyObject.constBegin(); + const auto signature = + oneTimeKeyForId["signatures"][user]["ed25519:"_ls % device] + .toString() + .toLatin1(); + auto signedObject = oneTimeKeyForId.toObject(); + signedObject.remove("unsigned"_ls); + signedObject.remove("signatures"_ls); + const auto signedData = + QJsonDocument(signedObject).toJson(QJsonDocument::Compact); + if (!verifier.ed25519Verify( + connection->edKeyForUserDevice(user, device).toLatin1(), + signedData, signature)) { + qWarning(E2EE) << "Failed to verify one-time-key signature for" + << user << device << ". Skipping this device."; + return false; + } + const auto recipientCurveKey = + connection->curveKeyForUserDevice(user, device); + connection->createOlmSession(recipientCurveKey, + oneTimeKeyForId["key"].toString()); + return true; } - void sendRoomKeyToDevices(const QByteArray& sessionId, const QByteArray& sessionKey, const QHash<QString, QStringList> devices, int index) + void sendRoomKeyToDevices(const QByteArray& sessionId, + const QByteArray& sessionKey, + const QMultiHash<QString, QString>& devices, + int index) { - qCDebug(E2EE) << "Sending room key to devices" << sessionId, sessionKey.toHex(); + qDebug(E2EE) << "Sending room key to devices:" << sessionId + << sessionKey.toHex(); QHash<QString, QHash<QString, QString>> hash; - for (const auto& user : devices.keys()) { - QHash<QString, QString> u; - for(const auto &device : devices[user]) { - if (!connection->hasOlmSession(user, device)) { - u[device] = "signed_curve25519"_ls; - qCDebug(E2EE) << "Adding" << user << device << "to keys to claim"; - } + for (const auto& [userId, deviceId] : asKeyValueRange(devices)) + if (!connection->hasOlmSession(userId, deviceId)) { + hash[userId].insert(deviceId, "signed_curve25519"_ls); + qDebug(E2EE) + << "Adding" << userId << deviceId << "to keys to claim"; } - if (!u.isEmpty()) { - hash[user] = u; - } - } - if (hash.isEmpty()) { + + if (hash.isEmpty()) return; - } + auto job = connection->callApi<ClaimKeysJob>(hash); - connect(job, &BaseJob::success, q, [job, this, sessionId, sessionKey, devices, index](){ + connect(job, &BaseJob::success, q, + [job, this, sessionId, sessionKey, devices, index] { Connection::UsersToDevicesToEvents usersToDevicesToEvents; const auto data = job->jsonData(); - for(const auto &user : devices.keys()) { - for(const auto &device : devices[user]) { - const auto recipientCurveKey = connection->curveKeyForUserDevice(user, device); - if (!connection->hasOlmSession(user, device)) { - qCDebug(E2EE) << "Creating a new session for" << user << device; - if(data["one_time_keys"][user][device].toObject().isEmpty()) { - qWarning() << "No one time key for" << user << device; - continue; - } - const auto keyId = data["one_time_keys"][user][device].toObject().keys()[0]; - const auto oneTimeKey = data["one_time_keys"][user][device][keyId]["key"].toString(); - const auto signature = data["one_time_keys"][user][device][keyId]["signatures"][user][QStringLiteral("ed25519:") + device].toString().toLatin1(); - auto signedData = data["one_time_keys"][user][device][keyId].toObject(); - signedData.remove("unsigned"); - signedData.remove("signatures"); - auto signatureMatch = QOlmUtility().ed25519Verify(connection->edKeyForUserDevice(user, device).toLatin1(), QJsonDocument(signedData).toJson(QJsonDocument::Compact), signature); - if (!signatureMatch) { - qCWarning(E2EE) << "Failed to verify one-time-key signature for" << user << device << ". Skipping this device."; - continue; - } else { - } - connection->createOlmSession(recipientCurveKey, oneTimeKey); - } - usersToDevicesToEvents[user][device] = payloadForUserDevice(user, device, sessionId, sessionKey); - } + for (const auto& [user, device] : asKeyValueRange(devices)) { + if (!connection->hasOlmSession(user, device) + && !createOlmSession( + user, device, + data["one_time_keys"][user][device].toObject())) + continue; + + usersToDevicesToEvents[user][device] = + payloadForUserDevice(user, device, sessionId, + sessionKey); } if (!usersToDevicesToEvents.empty()) { - connection->sendToDevices("m.room.encrypted", usersToDevicesToEvents); + connection->sendToDevices("m.room.encrypted"_ls, + usersToDevicesToEvents); QVector<std::tuple<QString, QString, QString>> receivedDevices; - for (const auto& user : devices.keys()) { - for (const auto& device : devices[user]) { - receivedDevices += {user, device, q->connection()->curveKeyForUserDevice(user, device) }; - } - } - connection->database()->setDevicesReceivedKey(q->id(), receivedDevices, sessionId, index); + receivedDevices.reserve(devices.size()); + for (const auto& [user, device] : asKeyValueRange(devices)) + receivedDevices.push_back( + { user, device, + connection->curveKeyForUserDevice(user, device) }); + + connection->database()->setDevicesReceivedKey(id, + receivedDevices, + sessionId, index); } }); } - void sendMegolmSession(const QHash<QString, QStringList>& devices) { + void sendMegolmSession(const QMultiHash<QString, QString>& devices) { // Save the session to this device const auto sessionId = currentOutboundMegolmSession->sessionId(); - const auto _sessionKey = currentOutboundMegolmSession->sessionKey(); - if(!_sessionKey) { + const auto sessionKey = currentOutboundMegolmSession->sessionKey(); + if(!sessionKey) { qCWarning(E2EE) << "Error loading session key"; return; } - const auto sessionKey = *_sessionKey; - const auto senderKey = q->connection()->olmAccount()->identityKeys().curve25519; // Send the session to other people - sendRoomKeyToDevices(sessionId, sessionKey, devices, currentOutboundMegolmSession->sessionMessageIndex()); + sendRoomKeyToDevices(sessionId, *sessionKey, devices, + currentOutboundMegolmSession->sessionMessageIndex()); } #endif // Quotient_E2EE_ENABLED @@ -592,7 +617,8 @@ Room::Room(Connection* connection, QString id, JoinState initialJoinState) } }); d->groupSessions = connection->loadRoomMegolmSessions(this); - d->currentOutboundMegolmSession = connection->loadCurrentOutboundMegolmSession(this); + d->currentOutboundMegolmSession = + connection->loadCurrentOutboundMegolmSession(this->id()); if (d->shouldRotateMegolmSession()) { d->currentOutboundMegolmSession = nullptr; } @@ -2101,12 +2127,12 @@ QString Room::Private::doSendEvent(const RoomEvent* pEvent) if (!hasValidMegolmSession() || shouldRotateMegolmSession()) { createMegolmSession(); } - const auto devicesWithoutKey = getDevicesWithoutKey(); - sendMegolmSession(devicesWithoutKey); + sendMegolmSession(getDevicesWithoutKey()); const auto encrypted = currentOutboundMegolmSession->encrypt(QJsonDocument(pEvent->fullJson()).toJson()); currentOutboundMegolmSession->setMessageCount(currentOutboundMegolmSession->messageCount() + 1); - connection->saveCurrentOutboundMegolmSession(q, currentOutboundMegolmSession); + connection->saveCurrentOutboundMegolmSession( + id, *currentOutboundMegolmSession); if(!encrypted) { qWarning(E2EE) << "Error encrypting message" << encrypted.error(); return {}; |