aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/connection.cpp45
-rw-r--r--lib/encryptionmanager.cpp352
-rw-r--r--lib/encryptionmanager.h20
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;