diff options
-rw-r--r-- | lib/encryptionmanager.cpp | 175 | ||||
-rw-r--r-- | lib/encryptionmanager.h | 7 |
2 files changed, 167 insertions, 15 deletions
diff --git a/lib/encryptionmanager.cpp b/lib/encryptionmanager.cpp index 22387cf9..e2834c45 100644 --- a/lib/encryptionmanager.cpp +++ b/lib/encryptionmanager.cpp @@ -9,6 +9,10 @@ #include <QtCore/QStringBuilder> #include <account.h> // QtOlm +#include <session.h> // QtOlm +#include <message.h> // QtOlm +#include <errors.h> // QtOlm +#include <utils.h> // QtOlm #include <functional> #include <memory> @@ -20,7 +24,8 @@ class EncryptionManager::Private { public: explicit Private(const QByteArray& encryptionAccountPickle, float signedKeysProportion, float oneTimeKeyThreshold) - : signedKeysProportion(move(signedKeysProportion)) + : q(nullptr) + , signedKeysProportion(move(signedKeysProportion)) , oneTimeKeyThreshold(move(oneTimeKeyThreshold)) { Q_ASSERT((0 <= signedKeysProportion) && (signedKeysProportion <= 1)); @@ -44,18 +49,23 @@ public: * until the limit is reached and it starts discarding keys, starting by * the oldest. */ - targetKeysNumber = olmAccount->maxOneTimeKeys(); // 2 // see note below + targetKeysNumber = olmAccount->maxOneTimeKeys() / 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; QScopedPointer<Account> olmAccount; @@ -74,6 +84,95 @@ public: } QHash<QString, int> oneTimeKeysToUploadCounts; QHash<QString, int> targetOneTimeKeyCounts; + + // A map from senderKey to InboundSession + QMap<QString, InboundSession*> sessions; // TODO: cache + void updateDeviceKeys( + const QHash<QString, QHash<QString, QueryKeysJob::DeviceInformation>>& + deviceKeys) + { + for (auto userId : deviceKeys.keys()) { + for (auto deviceId : deviceKeys.value(userId).keys()) { + QueryKeysJob::DeviceInformation info = + deviceKeys.value(userId).value(deviceId); + // TODO: ed25519Verify, etc + } + } + } + QString sessionDecrypt(Message* message, const QString& senderKey) + { + QString decrypted; + QList<InboundSession*> senderSessions = sessions.values(senderKey); + // Try to decrypt message body using one of the known sessions for that + // device + bool sessionsPassed = false; + for (auto senderSession : senderSessions) { + if (senderSession == senderSessions.last()) { + sessionsPassed = true; + } + try { + decrypted = senderSession->decrypt(message); + qCDebug(E2EE) + << "Success decrypting Olm event using existing session" + << senderSession->id(); + break; + } catch (OlmError* e) { + if (message->messageType() == 0) { + PreKeyMessage preKeyMessage = + PreKeyMessage(message->cipherText()); + if (senderSession->matches(&preKeyMessage, senderKey)) { + // 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->id() << "reason:" << e->what(); + return QString(); + } + } + // Simply keep trying otherwise + } + } + if (sessionsPassed || senderSessions.empty()) { + if (message->messageType() > 0) { + // 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. + InboundSession* newSession; + qCDebug(E2EE) << "try to establish new InboundSession with" << senderKey; + PreKeyMessage preKeyMessage = PreKeyMessage(message->cipherText()); + try { + newSession = new InboundSession(olmAccount.data(), + &preKeyMessage, + senderKey.toLatin1(), q); + } catch (OlmError* e) { + qCDebug(E2EE) << "Error decrypting pre-key message when trying " + "to establish a new session:" + << e->what(); + return QString(); + } + qCDebug(E2EE) << "Created new Olm session" << newSession->id(); + try { + decrypted = newSession->decrypt(message); + } catch (OlmError* e) { + qCDebug(E2EE) + << "Error decrypting pre-key message with new session" + << e->what(); + return QString(); + } + olmAccount->removeOneTimeKeys(newSession); + sessions.insert(senderKey, newSession); + } + return decrypted; + } }; EncryptionManager::EncryptionManager(const QByteArray& encryptionAccountPickle, @@ -83,7 +182,9 @@ EncryptionManager::EncryptionManager(const QByteArray& encryptionAccountPickle, , d(std::make_unique<Private>(std::move(encryptionAccountPickle), std::move(signedKeysProportion), std::move(oneTimeKeyThreshold))) -{} +{ + d->q = this; +} EncryptionManager::~EncryptionManager() = default; @@ -132,20 +233,19 @@ void EncryptionManager::uploadIdentityKeys(Connection* connection) d->olmAccount->sign(deviceKeysJsonObject) } } } }; + d->uploadIdentityKeysJob = connection->callApi<UploadKeysJob>(deviceKeys); connect(d->uploadIdentityKeysJob, &BaseJob::success, this, [this] { d->setOneTimeKeyCounts(d->uploadIdentityKeysJob->oneTimeKeyCounts()); - qDebug() << QString("Uploaded identity keys."); }); - d->uploadIdentityKeysJob = connection->callApi<UploadKeysJob>(deviceKeys); } void EncryptionManager::uploadOneTimeKeys(Connection* connection, bool forceUpdate) { if (forceUpdate || d->oneTimeKeyCounts.isEmpty()) { - auto job = connection->callApi<UploadKeysJob>(); - connect(job, &BaseJob::success, this, [job, this] { - d->setOneTimeKeyCounts(job->oneTimeKeyCounts()); + d->uploadOneTimeKeysInitJob = connection->callApi<UploadKeysJob>(); + connect(d->uploadOneTimeKeysInitJob, &BaseJob::success, this, [this] { + d->setOneTimeKeyCounts(d->uploadIdentityKeysJob->oneTimeKeyCounts()); }); } @@ -170,9 +270,17 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, if (oneTimeKeysCounter < signedKeysToUploadCount) { QJsonObject message { { QStringLiteral("key"), it.value().toString() } }; - key = d->olmAccount->sign(message); - keyType = SignedCurve25519Key; + 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; @@ -180,13 +288,50 @@ void EncryptionManager::uploadOneTimeKeys(Connection* connection, ++oneTimeKeysCounter; oneTimeKeys.insert(QString("%1:%2").arg(keyType).arg(keyId), key); } - - d->uploadOneTimeKeysJob = connection->callApi<UploadKeysJob>(none, - oneTimeKeys); + d->uploadOneTimeKeysJob = + connection->callApi<UploadKeysJob>(none, oneTimeKeys); + connect(d->uploadOneTimeKeysJob, &BaseJob::success, this, [this] { + d->setOneTimeKeyCounts(d->uploadOneTimeKeysJob->oneTimeKeyCounts()); + }); d->olmAccount->markKeysAsPublished(); - qDebug() << QString("Uploaded new one-time keys: %1 signed, %2 unsigned.") + qCDebug(E2EE) << QString("Uploaded new one-time keys: %1 signed, %2 unsigned.") .arg(signedKeysToUploadCount) - .arg(unsignedKeysToUploadCount); + .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) +{ + QString decrypted; + int type = personalCipherObject.value(TypeKeyL).toInt(-1); + QByteArray body = personalCipherObject.value(BodyKeyL).toString().toLatin1(); + if (type == 0) { + PreKeyMessage preKeyMessage { body }; + decrypted = d->sessionDecrypt(reinterpret_cast<Message*>(&preKeyMessage), + senderKey); + } else if (type == 1) { + Message message { body }; + decrypted = d->sessionDecrypt(&message, senderKey); + } + return decrypted; } QByteArray EncryptionManager::olmAccountPickle() diff --git a/lib/encryptionmanager.h b/lib/encryptionmanager.h index b210a85a..8f346d37 100644 --- a/lib/encryptionmanager.h +++ b/lib/encryptionmanager.h @@ -26,6 +26,13 @@ public: 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(); QtOlm::Account* account() const; |