diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/connection.cpp | 45 | ||||
-rw-r--r-- | lib/encryptionmanager.cpp | 352 | ||||
-rw-r--r-- | lib/encryptionmanager.h | 20 |
3 files changed, 69 insertions, 348 deletions
diff --git a/lib/connection.cpp b/lib/connection.cpp index 10256d9c..2d040e8a 100644 --- a/lib/connection.cpp +++ b/lib/connection.cpp @@ -118,7 +118,7 @@ public: #ifdef Quotient_E2EE_ENABLED std::unique_ptr<QOlmAccount> olmAccount; bool isUploadingKeys = false; - QScopedPointer<EncryptionManager> encryptionManager; + EncryptionManager *encryptionManager; #endif // Quotient_E2EE_ENABLED QPointer<GetWellknownJob> resolverJob = nullptr; @@ -194,17 +194,14 @@ public: EventPtr sessionDecryptMessage(const EncryptedEvent& encryptedEvent) { - qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; - return {}; #ifndef Quotient_E2EE_ENABLED qCWarning(E2EE) << "End-to-end encryption (E2EE) support is turned off."; return {}; -#else // Quotient_E2EE_ENABLED +#else if (encryptedEvent.algorithm() != OlmV1Curve25519AesSha2AlgoKey) return {}; - const auto identityKey = - encryptionManager->account()->identityKeys().curve25519; + const auto identityKey = olmAccount->identityKeys().curve25519; const auto personalCipherObject = encryptedEvent.ciphertext(identityKey); if (personalCipherObject.isEmpty()) { @@ -212,11 +209,11 @@ public: return {}; } const auto decrypted = encryptionManager->sessionDecryptMessage( - personalCipherObject, encryptedEvent.senderKey().toLatin1()); + personalCipherObject, encryptedEvent.senderKey().toLatin1(), olmAccount); if (decrypted.isEmpty()) { qCDebug(E2EE) << "Problem with new session from senderKey:" << encryptedEvent.senderKey() - << encryptionManager->account()->oneTimeKeys().keys; + << olmAccount->oneTimeKeys().keys; return {}; } @@ -233,22 +230,18 @@ public: // TODO: keys to constants const auto decryptedEventObject = decryptedEvent->fullJson(); - const auto recipient = - decryptedEventObject.value("recipient"_ls).toString(); + const auto recipient = decryptedEventObject.value("recipient"_ls).toString(); if (recipient != data->userId()) { qCDebug(E2EE) << "Found user" << recipient << "instead of us" << data->userId() << "in Olm plaintext"; return {}; } - const auto ourKey = - decryptedEventObject.value("recipient_keys"_ls).toObject() - .value(Ed25519Key).toString(); - if (ourKey - != QString::fromUtf8( - encryptionManager->account()->identityKeys().ed25519)) { + const auto ourKey = decryptedEventObject.value("recipient_keys"_ls).toObject() + .value(Ed25519Key).toString(); + if (ourKey != QString::fromUtf8(olmAccount->identityKeys().ed25519)) { qCDebug(E2EE) << "Found key" << ourKey << "instead of ours own ed25519 key" - << encryptionManager->account()->identityKeys().ed25519 + << olmAccount->identityKeys().ed25519 << "in Olm plaintext"; return {}; } @@ -266,6 +259,7 @@ public: Connection::Connection(const QUrl& server, QObject* parent) : QObject(parent), d(new Private(std::make_unique<ConnectionData>(server))) { + d->encryptionManager = new EncryptionManager(this); d->q = this; // All d initialization should occur before this line } @@ -791,21 +785,20 @@ void Connection::Private::consumePresenceData(Events&& presenceData) void Connection::Private::consumeToDeviceEvents(Events&& toDeviceEvents) { #ifdef Quotient_E2EE_ENABLED - // handling m.room_key to-device encrypted event - visitEach(toDeviceEvents, [this](const EncryptedEvent& ee) { - if (ee.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { - qCDebug(E2EE) << "Encrypted event" << ee.id() << "algorithm" - << ee.algorithm() << "is not supported"; + qWarning() << "Consuming to device events" << toDeviceEvents.size(); + if(toDeviceEvents.size() > 0) + visitEach(toDeviceEvents, [this](const EncryptedEvent& event) { + if (event.algorithm() != OlmV1Curve25519AesSha2AlgoKey) { + qCDebug(E2EE) << "Unsupported algorithm" << event.id() << "for event" << event.algorithm(); return; } - visit(*sessionDecryptMessage(ee), - [this, senderKey = ee.senderKey()](const RoomKeyEvent& roomKeyEvent) { + visit(*sessionDecryptMessage(event), + [this, senderKey = event.senderKey()](const RoomKeyEvent& roomKeyEvent) { if (auto* detectedRoom = q->room(roomKeyEvent.roomId())) { detectedRoom->handleRoomKeyEvent(roomKeyEvent, senderKey); } else { - qCDebug(E2EE) - << "Encrypted event room id" << roomKeyEvent.roomId() + qCDebug(E2EE) << "Encrypted event room id" << roomKeyEvent.roomId() << "is not found at the connection" << q->objectName(); } }, diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 53890fdb..b9bd6646 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -26,69 +26,16 @@ using std::move; class EncryptionManager::Private { public: - explicit Private(const QByteArray& encryptionAccountPickle, - float signedKeysProportion, float oneTimeKeyThreshold) + explicit Private() : q(nullptr) - , signedKeysProportion(move(signedKeysProportion)) - , oneTimeKeyThreshold(move(oneTimeKeyThreshold)) { - Q_ASSERT((0 <= signedKeysProportion) && (signedKeysProportion <= 1)); - Q_ASSERT((0 <= oneTimeKeyThreshold) && (oneTimeKeyThreshold <= 1)); - if (encryptionAccountPickle.isEmpty()) { - // new e2ee TODO: olmAccount.reset(new QOlmAccount()); - } else { - // new e2ee TODO: olmAccount.reset(new QOlmAccount(encryptionAccountPickle)); // TODO: passphrase even with qtkeychain? - } - /* - * Note about targetKeysNumber: - * - * From: https://github.com/Zil0/matrix-python-sdk/ - * File: matrix_client/crypto/olm_device.py - * - * Try to maintain half the number of one-time keys libolm can hold - * uploaded on the HS. This is because some keys will be claimed by - * peers but not used instantly, and we want them to stay in libolm, - * until the limit is reached and it starts discarding keys, starting by - * the oldest. - */ - targetKeysNumber = olmAccount->maxNumberOfOneTimeKeys() / 2; - targetOneTimeKeyCounts = { - { SignedCurve25519Key, - qRound(signedKeysProportion * targetKeysNumber) }, - { Curve25519Key, - qRound((1 - signedKeysProportion) * targetKeysNumber) } - }; - updateKeysToUpload(); } ~Private() = default; EncryptionManager* q; - UploadKeysJob* uploadIdentityKeysJob = nullptr; - UploadKeysJob* uploadOneTimeKeysInitJob = nullptr; - UploadKeysJob* uploadOneTimeKeysJob = nullptr; - QueryKeysJob* queryKeysJob = nullptr; - - std::unique_ptr<QOlmAccount> olmAccount; - - float signedKeysProportion; - float oneTimeKeyThreshold; - int targetKeysNumber; - - void updateKeysToUpload(); - bool oneTimeKeyShouldUpload(); - - QHash<QString, int> oneTimeKeyCounts; - void setOneTimeKeyCounts(const QHash<QString, int> oneTimeKeyCountsNewValue) - { - oneTimeKeyCounts = oneTimeKeyCountsNewValue; - updateKeysToUpload(); - } - QHash<QString, int> oneTimeKeysToUploadCounts; - QHash<QString, int> targetOneTimeKeyCounts; - // A map from senderKey to InboundSession - QMap<QString, std::unique_ptr<QOlmSession>> sessions; // TODO: cache + std::map<QString, std::unique_ptr<QOlmSession>> sessions; // TODO: cache void updateDeviceKeys( const QHash<QString, QHash<QString, QueryKeysJob::DeviceInformation>>& deviceKeys) @@ -100,279 +47,76 @@ public: } } } - QString sessionDecrypt(const QOlmMessage& message, const QString& senderKey) + QString sessionDecryptPrekey(const QOlmMessage& message, const QString &senderKey, std::unique_ptr<QOlmAccount>& olmAccount) { - // Try to decrypt message body using one of the known sessions for that - // device - /*bool sessionsPassed = false; - // new e2ee TODO: - for (auto &senderSession : sessions) { - if (senderSession == sessions.last()) { - sessionsPassed = true; - } - - const auto decryptedResult = senderSession->decrypt(message); - if (std::holds_alternative<QString>(decryptedResult)) { - qCDebug(E2EE) - << "Success decrypting Olm event using existing session" - << senderSession->sessionId(); - return std::get<QString>(decryptedResult); - } else { - const auto error = std::get<QOlmError>(decryptedResult); - if (message.type() == QOlmMessage::PreKey) { - const auto matches = senderSession->matchesInboundSessionFrom(senderKey, message); - if (auto hasMatch = std::get_if<bool>(&matches)) { - if (hasMatch) { - // We had a matching session for a pre-key message, but - // it didn't work. This means something is wrong, so we - // fail now. - qCDebug(E2EE) - << "Error decrypting pre-key message with existing " - "Olm session" - << senderSession->sessionId() << "reason:" << error; - return QString(); - } - } + Q_ASSERT(message.type() == QOlmMessage::PreKey); + for(auto& session : sessions) { + const auto matches = session.second->matchesInboundSessionFrom(senderKey, message); + if(std::holds_alternative<bool>(matches) && std::get<bool>(matches)) { + qCDebug(E2EE) << "Found inbound session"; + const auto result = session.second->decrypt(message); + if(std::holds_alternative<QString>(result)) { + return std::get<QString>(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message"; + return {}; } - // Simply keep trying otherwise } } - if (sessionsPassed || sessions.empty()) { - if (message.type() != QOlmMessage::PreKey) { - // Not a pre-key message, we should have had a matching session - if (!sessions.empty()) { - qCDebug(E2EE) << "Error decrypting with existing sessions"; - return QString(); - } - qCDebug(E2EE) << "No existing sessions"; - return QString(); - } - // We have a pre-key message without any matching session, in this - // case we should try to create one. - qCDebug(E2EE) << "try to establish new InboundSession with" << senderKey; - QOlmMessage preKeyMessage = QOlmMessage(message.toCiphertext(), QOlmMessage::PreKey); - // new e2ee TODO: - //const auto sessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage); - - if (const auto error = std::get_if<QOlmError>(&sessionResult)) { - qCDebug(E2EE) << "Error decrypting pre-key message when trying " - "to establish a new session:" - << error; - return QString(); - } - - const auto newSession = std::get<std::unique_ptr<QOlmSession>>(olmAccount->createInboundSessionFrom(senderKey.toUtf8(), preKeyMessage)); - - qCDebug(E2EE) << "Created new Olm session" << newSession->sessionId(); - - const auto decryptedResult = newSession->decrypt(message); - if (const auto error = std::get_if<QOlmError>(&decryptedResult)) { - qCDebug(E2EE) - << "Error decrypting pre-key message with new session" - << error; - return QString(); - } - - if (auto error = olmAccount->removeOneTimeKeys(newSession)) { - qCDebug(E2EE) - << "Error removing one time keys" - << error.value(); + qCDebug(E2EE) << "Creating new inbound session"; + auto newSessionResult = olmAccount->createInboundSessionFrom(senderKey.toUtf8(), message); + if(std::holds_alternative<QOlmError>(newSessionResult)) { + qCWarning(E2EE) << "Failed to create inbound session for" << senderKey; + return {}; + } + std::unique_ptr<QOlmSession> newSession = std::move(std::get<std::unique_ptr<QOlmSession>>(newSessionResult)); + // TODO Error handling? + olmAccount->removeOneTimeKeys(newSession); + const auto result = newSession->decrypt(message); + sessions[senderKey] = std::move(newSession); + if(std::holds_alternative<QString>(result)) { + return std::get<QString>(result); + } else { + qCDebug(E2EE) << "Failed to decrypt prekey message with new session"; + return {}; + } + } + QString sessionDecryptGeneral(const QOlmMessage& message, const QString &senderKey) + { + Q_ASSERT(message.type() == QOlmMessage::General); + for(auto& session : sessions) { + const auto result = session.second->decrypt(message); + if(std::holds_alternative<QString>(result)) { + return std::get<QString>(result); } - //sessions.insert(senderKey, std::move(newSession)); TODO - //return std::get<QString>(decryptedResult); - }*/ - return QString(); + } + qCWarning(E2EE) << "Failed to decrypt message"; + return {}; } }; -EncryptionManager::EncryptionManager(const QByteArray& encryptionAccountPickle, - float signedKeysProportion, - float oneTimeKeyThreshold, QObject* parent) +EncryptionManager::EncryptionManager(QObject* parent) : QObject(parent) - , d(std::make_unique<Private>(std::move(encryptionAccountPickle), - std::move(signedKeysProportion), - std::move(oneTimeKeyThreshold))) + , d(std::make_unique<Private>()) { d->q = this; } EncryptionManager::~EncryptionManager() = default; -void EncryptionManager::uploadIdentityKeys(Connection* connection) -{ - // https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-keys-upload - DeviceKeys deviceKeys { - /* - * The ID of the user the device belongs to. Must match the user ID used - * when logging in. The ID of the device these keys belong to. Must - * match the device ID used when logging in. The encryption algorithms - * supported by this device. - */ - connection->userId(), - connection->deviceId(), - SupportedAlgorithms, - /* - * Public identity keys. The names of the properties should be in the - * format <algorithm>:<device_id>. The keys themselves should be encoded - * as specified by the key algorithm. - */ - { { Curve25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->identityKeys().curve25519 }, - { Ed25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->identityKeys().curve25519 } }, - /* signatures should be provided after the unsigned deviceKeys - generation */ - {} - }; - - QJsonObject deviceKeysJsonObject = toJson(deviceKeys); - /* additionally removing signatures key, - * since we could not initialize deviceKeys - * without an empty signatures value: - */ - deviceKeysJsonObject.remove(QStringLiteral("signatures")); - /* - * Signatures for the device key object. - * A map from user ID, to a map from <algorithm>:<device_id> to the - * signature. The signature is calculated using the process called Signing - * JSON. - */ - deviceKeys.signatures = { - { connection->userId(), - { { Ed25519Key + QStringLiteral(":") + connection->deviceId(), - d->olmAccount->sign(deviceKeysJsonObject) } } } - }; - - d->uploadIdentityKeysJob = connection->callApi<UploadKeysJob>(deviceKeys); - connect(d->uploadIdentityKeysJob, &BaseJob::success, this, [this] { - d->setOneTimeKeyCounts(d->uploadIdentityKeysJob->oneTimeKeyCounts()); - }); -} - -void EncryptionManager::uploadOneTimeKeys(Connection* connection, - bool forceUpdate) -{ - if (forceUpdate || d->oneTimeKeyCounts.isEmpty()) { - d->uploadOneTimeKeysInitJob = connection->callApi<UploadKeysJob>(); - connect(d->uploadOneTimeKeysInitJob, &BaseJob::success, this, [this] { - d->setOneTimeKeyCounts(d->uploadOneTimeKeysInitJob->oneTimeKeyCounts()); - }); - } - - int signedKeysToUploadCount = - d->oneTimeKeysToUploadCounts.value(SignedCurve25519Key, 0); - int unsignedKeysToUploadCount = - d->oneTimeKeysToUploadCounts.value(Curve25519Key, 0); - - d->olmAccount->generateOneTimeKeys(signedKeysToUploadCount - + unsignedKeysToUploadCount); - - QHash<QString, QVariant> oneTimeKeys = {}; - const auto& olmAccountCurve25519OneTimeKeys = d->olmAccount->oneTimeKeys().curve25519(); - - int oneTimeKeysCounter = 0; - for (auto it = olmAccountCurve25519OneTimeKeys.cbegin(); - it != olmAccountCurve25519OneTimeKeys.cend(); ++it) { - QString keyId = it.key(); - QString keyType; - QVariant key; - if (oneTimeKeysCounter < signedKeysToUploadCount) { - QJsonObject message { { QStringLiteral("key"), - it.value() } }; - - QByteArray signedMessage = d->olmAccount->sign(message); - QJsonObject signatures { - { connection->userId(), - QJsonObject { { Ed25519Key + QStringLiteral(":") - + connection->deviceId(), - QString::fromUtf8(signedMessage) } } } - }; - message.insert(QStringLiteral("signatures"), signatures); - key = message; - keyType = SignedCurve25519Key; - } else { - key = it.value(); - keyType = Curve25519Key; - } - ++oneTimeKeysCounter; - oneTimeKeys.insert(QString("%1:%2").arg(keyType).arg(keyId), key); - } - d->uploadOneTimeKeysJob = - connection->callApi<UploadKeysJob>(none, oneTimeKeys); - connect(d->uploadOneTimeKeysJob, &BaseJob::success, this, [this] { - d->setOneTimeKeyCounts(d->uploadOneTimeKeysJob->oneTimeKeyCounts()); - }); - // new e2ee TODO: d->olmAccount->markKeysAsPublished(); - qCDebug(E2EE) << QString("Uploaded new one-time keys: %1 signed, %2 unsigned.") - .arg(signedKeysToUploadCount) - .arg(unsignedKeysToUploadCount); -} - -void EncryptionManager::updateOneTimeKeyCounts( - Connection* connection, const QHash<QString, int>& deviceOneTimeKeysCount) -{ - d->oneTimeKeyCounts = deviceOneTimeKeysCount; - if (d->oneTimeKeyShouldUpload()) { - qCDebug(E2EE) << "Uploading new one-time keys."; - uploadOneTimeKeys(connection); - } -} - -void Quotient::EncryptionManager::updateDeviceKeys( - Connection* connection, const QHash<QString, QStringList>& deviceKeys) -{ - d->queryKeysJob = connection->callApi<QueryKeysJob>(deviceKeys); - connect(d->queryKeysJob, &BaseJob::success, this, - [this] { d->updateDeviceKeys(d->queryKeysJob->deviceKeys()); }); -} - QString EncryptionManager::sessionDecryptMessage( - const QJsonObject& personalCipherObject, const QByteArray& senderKey) + const QJsonObject& personalCipherObject, const QByteArray& senderKey, std::unique_ptr<QOlmAccount>& account) { QString decrypted; int type = personalCipherObject.value(TypeKeyL).toInt(-1); QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); if (type == 0) { QOlmMessage preKeyMessage(body, QOlmMessage::PreKey); - decrypted = d->sessionDecrypt(preKeyMessage, senderKey); + decrypted = d->sessionDecryptPrekey(preKeyMessage, senderKey, account); } else if (type == 1) { - QOlmMessage message(body, QOlmMessage::PreKey); - decrypted = d->sessionDecrypt(message, senderKey); + QOlmMessage message(body, QOlmMessage::General); + decrypted = d->sessionDecryptGeneral(message, senderKey); } return decrypted; } - -QByteArray EncryptionManager::olmAccountPickle() -{ - // new e2ee TODO: return d->olmAccount->pickle(); // TODO: passphrase even with qtkeychain? - return {}; -} - -QOlmAccount *EncryptionManager::account() const -{ - return d->olmAccount.get(); -} - -void EncryptionManager::Private::updateKeysToUpload() -{ - for (auto it = targetOneTimeKeyCounts.cbegin(); - it != targetOneTimeKeyCounts.cend(); ++it) { - int numKeys = oneTimeKeyCounts.value(it.key(), 0); - int numToCreate = qMax(it.value() - numKeys, 0); - oneTimeKeysToUploadCounts.insert(it.key(), numToCreate); - } -} - -bool EncryptionManager::Private::oneTimeKeyShouldUpload() -{ - if (oneTimeKeyCounts.empty()) - return true; - for (auto it = targetOneTimeKeyCounts.cbegin(); - it != targetOneTimeKeyCounts.cend(); ++it) { - if (oneTimeKeyCounts.value(it.key(), 0) - < it.value() * oneTimeKeyThreshold) - return true; - } - return false; -} #endif // Quotient_E2EE_ENABLED diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index 9d2c8138..17f4f853 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -17,26 +17,10 @@ class EncryptionManager : public QObject { Q_OBJECT public: - // TODO: store constats separately? - // TODO: 0.5 oneTimeKeyThreshold instead of 0.1? - explicit EncryptionManager( - const QByteArray& encryptionAccountPickle = QByteArray(), - float signedKeysProportion = 1, float oneTimeKeyThreshold = float(0.1), - QObject* parent = nullptr); + explicit EncryptionManager(QObject* parent = nullptr); ~EncryptionManager(); - - void uploadIdentityKeys(Connection* connection); - void uploadOneTimeKeys(Connection* connection, bool forceUpdate = false); - void - updateOneTimeKeyCounts(Connection* connection, - const QHash<QString, int>& deviceOneTimeKeysCount); - void updateDeviceKeys(Connection* connection, - const QHash<QString, QStringList>& deviceKeys); QString sessionDecryptMessage(const QJsonObject& personalCipherObject, - const QByteArray& senderKey); - QByteArray olmAccountPickle(); - - QOlmAccount* account() const; + const QByteArray& senderKey, std::unique_ptr<QOlmAccount>& account); private: class Private; |