diff --git a/Quotient/connection.cpp b/Quotient/connection.cpp index 42616387f..7cda36603 100644 --- a/Quotient/connection.cpp +++ b/Quotient/connection.cpp @@ -1828,6 +1828,32 @@ void Connection::encryptionUpdate(const Room* room, const QList& invited) } } +void Connection::requestKeyFromDevices(event_type_t name, + const std::function& then) +{ + UsersToDevicesToContent content; + const auto& requestId = generateTxnId(); + const QJsonObject eventContent{ { "action"_ls, "request"_ls }, + { "name"_ls, name }, + { "request_id"_ls, requestId }, + { "requesting_device_id"_ls, deviceId() } }; + for (const auto& deviceId : devicesForUser(userId())) { + content[userId()][deviceId] = eventContent; + } + sendToDevices("m.secret.request"_ls, content); + connectUntil(this, &Connection::secretReceived, this, + [this, requestId, then, name](const QString& receivedRequestId, + const QString& secret) { + if (requestId != receivedRequestId) { + return false; + } + const auto& key = QByteArray::fromBase64(secret.toLatin1()); + database()->storeEncrypted(name, key); + then(key); + return true; + }); +} + QJsonObject Connection::decryptNotification(const QJsonObject& notification) { if (auto r = room(notification[RoomIdKey].toString())) diff --git a/Quotient/connection.h b/Quotient/connection.h index b32cdc089..aaa76c10e 100644 --- a/Quotient/connection.h +++ b/Quotient/connection.h @@ -408,7 +408,11 @@ class QUOTIENT_API Connection : public QObject { QJsonObject decryptNotification(const QJsonObject ¬ification); QStringList devicesForUser(const QString& userId) const; Q_INVOKABLE bool isQueryingKeys() const; + + void requestKeyFromDevices( + event_type_t name, const std::function& then = [](auto) {}); #endif // Quotient_E2EE_ENABLED + Q_INVOKABLE Quotient::SyncJob* syncJob() const; Q_INVOKABLE QString nextBatchToken() const; Q_INVOKABLE int millisToReconnect() const; diff --git a/Quotient/e2ee/sssshandler.cpp b/Quotient/e2ee/sssshandler.cpp index 33cd70ae4..30f00783f 100644 --- a/Quotient/e2ee/sssshandler.cpp +++ b/Quotient/e2ee/sssshandler.cpp @@ -12,37 +12,39 @@ #include "../database.h" #include "../logging_categories_p.h" -#include "../qt_connection_util.h" #include "../room.h" using namespace Quotient; DEFINE_SIMPLE_EVENT(SecretStorageDefaultKeyEvent, Event, "m.secret_storage.default_key", QString, key, "key"); -DEFINE_SIMPLE_EVENT(CrossSigningMasterKey, Event, "m.cross_signing.master", QJsonObject, encrypted, "encrypted") -DEFINE_SIMPLE_EVENT(CrossSigningSelfSigningKey, Event, "m.cross_signing.self_signing", QJsonObject, encrypted, "encrypted") -DEFINE_SIMPLE_EVENT(CrossSigningUserSigningKey, Event, "m.cross_signing.user_signing", QJsonObject, encrypted, "encrypted") -DEFINE_SIMPLE_EVENT(MegolmBackupKey, Event, "m.megolm_backup.v1", QJsonObject, encrypted, "encrypted") -template -QByteArray SSSSHandler::decryptKey(const QByteArray& decryptionKey) +constexpr inline auto MegolmBackupKey = "m.megolm_backup.v1"_ls; +constexpr inline auto CrossSigningMasterKey = "m.cross_signing.master"_ls; +constexpr inline auto CrossSigningSelfSigningKey = "m.cross_signing.self_signing"_ls; +constexpr inline auto CrossSigningUserSigningKey = "m.cross_signing.user_signing"_ls; + +QByteArray SSSSHandler::decryptKey(event_type_t keyType, const QByteArray& decryptionKey) { Q_ASSERT(m_connection); - if (!m_connection->hasAccountData(SecretStorageDefaultKeyEvent::TypeId)) { + const auto defaultKeyEvent = m_connection->accountData(); + if (!defaultKeyEvent) { qCWarning(E2EE) << "No default secret storage key"; emit error(NoKeyError); return {}; } - const auto defaultKey = m_connection->accountData()->key(); - if (!m_connection->hasAccountData(EventType::TypeId)) { - qWarning() << "No account data for key" << EventType::TypeId; + const auto& encryptedKeyObject = m_connection->accountData(keyType); + if (!encryptedKeyObject) { + qWarning() << "No account data for key" << keyType; emit error(NoKeyError); return {}; } - const auto& encrypted = m_connection->accountData()->encrypted().value(defaultKey).toObject(); + const auto& encrypted = encryptedKeyObject->contentPart("encrypted"_ls) + .value(defaultKeyEvent->key()) + .toObject(); - auto hkdfResult = hkdfSha256(decryptionKey, QByteArray(32, u'\0'), EventType::TypeId.data()); + auto hkdfResult = hkdfSha256(decryptionKey, QByteArray(32, u'\0'), keyType.data()); if (!hkdfResult.has_value()) { - qCWarning(E2EE) << "Failed to calculate HKDF for" << EventType::TypeId; + qCWarning(E2EE) << "Failed to calculate HKDF for" << keyType; emit error(DecryptionError); } const auto& keys = hkdfResult.value(); @@ -50,11 +52,11 @@ QByteArray SSSSHandler::decryptKey(const QByteArray& decryptionKey) auto rawCipher = QByteArray::fromBase64(encrypted["ciphertext"_ls].toString().toLatin1()); auto result = hmacSha256(keys.mac(), rawCipher); if (!result.has_value()) { - qCWarning(E2EE) << "Failed to calculate HMAC for" << EventType::TypeId; + qCWarning(E2EE) << "Failed to calculate HMAC for" << keyType; emit error(DecryptionError); } if (QString::fromLatin1(result.value().toBase64()) != encrypted["mac"_ls].toString()) { - qCWarning(E2EE) << "MAC mismatch for" << EventType::TypeId; + qCWarning(E2EE) << "MAC mismatch for" << keyType; emit error(DecryptionError); return {}; } @@ -63,11 +65,11 @@ QByteArray SSSSHandler::decryptKey(const QByteArray& decryptionKey) asCBytes(QByteArray::fromBase64( encrypted["iv"_ls].toString().toLatin1()))); if (!decryptResult.has_value()) { - qCWarning(E2EE) << "Failed to decrypt for" << EventType::TypeId; + qCWarning(E2EE) << "Failed to decrypt for" << keyType; emit error(DecryptionError); } auto key = QByteArray::fromBase64(decryptResult.value()); - m_connection->database()->storeEncrypted(EventType::TypeId, key); + m_connection->database()->storeEncrypted(keyType, key); return key; } @@ -77,39 +79,14 @@ void SSSSHandler::unlockSSSSFromPassword(const QString& password) calculateDefaultKey(password.toLatin1(), true); } -void SSSSHandler::requestKeyFromDevices(const QString& name, const std::function& then) -{ - Connection::UsersToDevicesToContent content; - const auto& requestId = m_connection->generateTxnId(); - const QJsonObject eventContent{ { "action"_ls, "request"_ls }, - { "name"_ls, name }, - { "request_id"_ls, requestId }, - { "requesting_device_id"_ls, - m_connection->deviceId() } }; - for (const auto& deviceId : m_connection->devicesForUser(m_connection->userId())) { - content[m_connection->userId()][deviceId] = eventContent; - } - m_connection->sendToDevices("m.secret.request"_ls, content); - connectUntil(m_connection.data(), &Connection::secretReceived, this, [this, requestId, then, name](const QString& receivedRequestId, const QString& secret) { - if (requestId != receivedRequestId) { - return false; - } - const auto& key = QByteArray::fromBase64(secret.toLatin1()); - m_connection->database()->storeEncrypted(name, key); - then(key); - return true; - }); -} - void SSSSHandler::unlockSSSSFromCrossSigning() { Q_ASSERT(m_connection); - requestKeyFromDevices("m.megolm_backup.v1"_ls, [this](const QByteArray &key){ + m_connection->requestKeyFromDevices(MegolmBackupKey, [this](const QByteArray &key){ loadMegolmBackup(key); }); - requestKeyFromDevices("m.cross_signing.user_signing"_ls, [](const QByteArray&){}); - requestKeyFromDevices("m.cross_signing.self_signing"_ls, [](const QByteArray&){}); - requestKeyFromDevices("m.cross_signing.master"_ls, [](const QByteArray&){}); + for (auto k : {CrossSigningUserSigningKey, CrossSigningSelfSigningKey, CrossSigningMasterKey}) + m_connection->requestKeyFromDevices(k); } Connection* SSSSHandler::connection() const @@ -238,12 +215,12 @@ void SSSSHandler::calculateDefaultKey(const QByteArray& secret, emit keyBackupUnlocked(); - auto megolmDecryptionKey = decryptKey(key); + auto megolmDecryptionKey = decryptKey(MegolmBackupKey, key); // These keys are only decrypted since this will automatically store them locally - decryptKey(key); - decryptKey(key); - decryptKey(key); + decryptKey(CrossSigningSelfSigningKey, key); + decryptKey(CrossSigningUserSigningKey, key); + decryptKey(CrossSigningMasterKey, key); if (megolmDecryptionKey.isEmpty()) { qCWarning(E2EE) << "No megolm decryption key"; emit error(NoKeyError); diff --git a/Quotient/e2ee/sssshandler.h b/Quotient/e2ee/sssshandler.h index cf52844b8..68f2c40d4 100644 --- a/Quotient/e2ee/sssshandler.h +++ b/Quotient/e2ee/sssshandler.h @@ -7,8 +7,8 @@ #include #include "../connection.h" -#include "../event.h" +namespace Quotient { class QUOTIENT_API SSSSHandler : public QObject { Q_OBJECT @@ -23,7 +23,7 @@ class QUOTIENT_API SSSSHandler : public QObject InvalidSignatureError, UnsupportedAlgorithmError, }; - Q_ENUM(Error); + Q_ENUM(Error) using QObject::QObject; @@ -36,8 +36,8 @@ class QUOTIENT_API SSSSHandler : public QObject //! \brief Unlock the secret backup from the given security key Q_INVOKABLE void unlockSSSSFromSecurityKey(const QString& key); - Quotient::Connection* connection() const; - void setConnection(Quotient::Connection* connection); + Connection* connection() const; + void setConnection(Connection* connection); Q_SIGNALS: void keyBackupUnlocked(); @@ -45,12 +45,12 @@ class QUOTIENT_API SSSSHandler : public QObject void connectionChanged(); private: - QPointer m_connection; + QPointer m_connection; //! \brief Decrypt the key with this name from the account data - template QByteArray decryptKey(const QByteArray& decryptionKey); + QByteArray decryptKey(event_type_t keyType, const QByteArray& decryptionKey); void loadMegolmBackup(const QByteArray& megolmDecryptionKey); void calculateDefaultKey(const QByteArray& secret, bool requirePassphrase); - void requestKeyFromDevices(const QString& name, const std::function& then); }; +} // namespace Quotient