diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index e9bb794a50..a270ad7624 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -66,7 +66,7 @@ Pod::Spec.new do |s| # Experimental / NOT production-ready Rust-based crypto library s.subspec 'CryptoSDK' do |ss| - ss.dependency 'MatrixSDKCrypto', '0.1.4', :configurations => ["DEBUG"] + ss.dependency 'MatrixSDKCrypto', '0.1.5', :configurations => ["DEBUG"] end end diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 5cd93892b9..71a11f0442 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -500,8 +500,8 @@ 32AF9285240EA2430008A0FD /* MXSecretShareRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AF9282240EA2430008A0FD /* MXSecretShareRequest.h */; }; 32AF9286240EA2430008A0FD /* MXSecretShareRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32AF9283240EA2430008A0FD /* MXSecretShareRequest.m */; }; 32AF9287240EA2430008A0FD /* MXSecretShareRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32AF9283240EA2430008A0FD /* MXSecretShareRequest.m */; }; - 32AF928A240EA3880008A0FD /* MXSecretShareSend.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AF9288240EA3880008A0FD /* MXSecretShareSend.h */; }; - 32AF928B240EA3880008A0FD /* MXSecretShareSend.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AF9288240EA3880008A0FD /* MXSecretShareSend.h */; }; + 32AF928A240EA3880008A0FD /* MXSecretShareSend.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AF9288240EA3880008A0FD /* MXSecretShareSend.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32AF928B240EA3880008A0FD /* MXSecretShareSend.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AF9288240EA3880008A0FD /* MXSecretShareSend.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32AF928C240EA3880008A0FD /* MXSecretShareSend.m in Sources */ = {isa = PBXBuildFile; fileRef = 32AF9289240EA3880008A0FD /* MXSecretShareSend.m */; }; 32AF928D240EA3880008A0FD /* MXSecretShareSend.m in Sources */ = {isa = PBXBuildFile; fileRef = 32AF9289240EA3880008A0FD /* MXSecretShareSend.m */; }; 32AF928F24110ADD0008A0FD /* MXSecretShareManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 32AF928E24110ADD0008A0FD /* MXSecretShareManager_Private.h */; }; @@ -1941,6 +1941,8 @@ ED8F1D3C2885BB2D00F897E7 /* MXCryptoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */; }; EDA2CDD628F5C4230088ACE7 /* MXQRCodeTransactionV2UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA2CDD528F5C4230088ACE7 /* MXQRCodeTransactionV2UnitTests.swift */; }; EDA2CDD728F5C4230088ACE7 /* MXQRCodeTransactionV2UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA2CDD528F5C4230088ACE7 /* MXQRCodeTransactionV2UnitTests.swift */; }; + EDA69340290BA92E00223252 /* MXCryptoMachineUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA6933F290BA92E00223252 /* MXCryptoMachineUnitTests.swift */; }; + EDA69341290BA92E00223252 /* MXCryptoMachineUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDA6933F290BA92E00223252 /* MXCryptoMachineUnitTests.swift */; }; EDAAC41928E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41828E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift */; }; EDAAC41A28E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41828E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift */; }; EDAAC41C28E30F3C00DD89B5 /* (null) in Headers */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (Public, ); }; }; @@ -3059,6 +3061,7 @@ ED8F1D332885ADE200F897E7 /* MXCryptoProtocolStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoProtocolStubs.swift; sourceTree = ""; }; ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoProtocols.swift; sourceTree = ""; }; EDA2CDD528F5C4230088ACE7 /* MXQRCodeTransactionV2UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXQRCodeTransactionV2UnitTests.swift; sourceTree = ""; }; + EDA6933F290BA92E00223252 /* MXCryptoMachineUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoMachineUnitTests.swift; sourceTree = ""; }; EDAAC41228E2F86800DD89B5 /* MXCryptoSecretStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXCryptoSecretStore.h; sourceTree = ""; }; EDAAC41828E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoSecretStoreV2.swift; sourceTree = ""; }; EDAAC42328E3177000DD89B5 /* MXRecoveryServiceDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRecoveryServiceDependencies.swift; sourceTree = ""; }; @@ -5320,6 +5323,7 @@ isa = PBXGroup; children = ( ED8F1D332885ADE200F897E7 /* MXCryptoProtocolStubs.swift */, + EDA6933F290BA92E00223252 /* MXCryptoMachineUnitTests.swift */, ED2DD11B286C4F3E00F06731 /* MXCryptoRequestsUnitTests.swift */, ED8F1D312885AC5700F897E7 /* Device+Stub.swift */, ); @@ -7155,6 +7159,7 @@ 32832B5E1BCC048300241108 /* MXStoreNoStoreTests.m in Sources */, A816247C25F60C7700A46F05 /* MXDeviceListOperationsPoolUnitTests.swift in Sources */, B1660F1C260A20B900C3AA12 /* MXSpaceServiceTest.swift in Sources */, + EDA69340290BA92E00223252 /* MXCryptoMachineUnitTests.swift in Sources */, ED35652C281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift in Sources */, 32C9B71823E81A1C00C6F30A /* MXCrossSigningVerificationTests.m in Sources */, 323C5A081A70E53500FB0549 /* MXToolsUnitTests.m in Sources */, @@ -7786,6 +7791,7 @@ 32C9B71923E81A1C00C6F30A /* MXCrossSigningVerificationTests.m in Sources */, B1E09A1D2397FCE90057C069 /* MXCryptoKeyVerificationTests.m in Sources */, B1E09A472397FD990057C069 /* MXEventScanStoreUnitTests.m in Sources */, + EDA69341290BA92E00223252 /* MXCryptoMachineUnitTests.swift in Sources */, ED35652D281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift in Sources */, B1E09A3D2397FD820057C069 /* MXStoreFileStoreTests.m in Sources */, 32CEEF3E23AD134A0039BA98 /* MXCrossSigningTests.m in Sources */, diff --git a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift index 744f127836..c2336392cb 100644 --- a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift +++ b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift @@ -121,7 +121,7 @@ class MXCrossSigningV2: NSObject, MXCrossSigning { Task { do { - try await crossSigning.downloadKeys(users: [crossSigning.userId]) + try await crossSigning.updateTrackedUsers(users: [crossSigning.userId]) myUserCrossSigningKeys = infoSource.crossSigningInfo(userId: crossSigning.userId) log.debug("Cross signing state refreshed") diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index 08e3e1d697..5202bb6d8f 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -163,13 +163,17 @@ extension MXCryptoMachine: MXCryptoSyncing { unusedFallbackKeys: unusedFallbackKeys ) - guard let json = MXTools.deserialiseJSONString(result) as? [AnyHashable: Any] else { - log.error("Result cannot be serialized", context: [ + guard + let json = MXTools.deserialiseJSONString(result) as? [Any], + let toDevice = MXToDeviceSyncResponse(fromJSON: ["events": json]) + else { + log.failure("Result cannot be serialized", context: [ "result": result ]) return MXToDeviceSyncResponse() } - return MXToDeviceSyncResponse(fromJSON: json) + + return toDevice } func processOutgoingRequests() async throws { @@ -312,10 +316,32 @@ extension MXCryptoMachine: MXCryptoUserIdentitySource { } } - func downloadKeys(users: [String]) async throws { - try await handleRequest( - .keysQuery(requestId: UUID().uuidString, users: users) - ) + func isUserTracked(userId: String) -> Bool { + do { + return try machine.isUserTracked(userId: userId) + } catch { + log.error("Failed checking user tracking") + return false + } + } + + func updateTrackedUsers(users: [String]) async throws { + machine.updateTrackedUsers(users: users) + try await withThrowingTaskGroup(of: Void.self) { [weak self] group in + guard let self = self else { return } + + for request in try machine.outgoingRequests() { + guard case .keysQuery = request else { + continue + } + + group.addTask { + try await self.handleRequest(request) + } + } + + try await group.waitForAll() + } } func manuallyVerifyUser(userId: String) async throws { @@ -353,9 +379,8 @@ extension MXCryptoMachine: MXCryptoRoomEventEncrypting { func encryptRoomEvent( content: [AnyHashable : Any], roomId: String, - eventType: String, - users: [String] - ) async throws -> [String : Any] { + eventType: String + ) throws -> [String : Any] { guard let content = MXTools.serialiseJSONObject(content) else { throw Error.cannotSerialize } @@ -424,25 +449,6 @@ extension MXCryptoMachine: MXCryptoRoomEventEncrypting { // MARK: - Private - private func updateTrackedUsers(users: [String]) async throws { - machine.updateTrackedUsers(users: users) - try await withThrowingTaskGroup(of: Void.self) { [weak self] group in - guard let self = self else { return } - - for request in try machine.outgoingRequests() { - guard case .keysQuery = request else { - continue - } - - group.addTask { - try await self.handleRequest(request) - } - } - - try await group.waitForAll() - } - } - private func getMissingSessions(users: [String]) async throws { guard let request = try machine.getMissingSessions(users: users), diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift index 1cce6bf48b..c2a6e670f1 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift @@ -52,7 +52,8 @@ protocol MXCryptoDevicesSource: MXCryptoIdentity { protocol MXCryptoUserIdentitySource: MXCryptoIdentity { func userIdentity(userId: String) -> UserIdentity? func isUserVerified(userId: String) -> Bool - func downloadKeys(users: [String]) async throws + func isUserTracked(userId: String) -> Bool + func updateTrackedUsers(users: [String]) async throws func manuallyVerifyUser(userId: String) async throws func manuallyVerifyDevice(userId: String, deviceId: String) async throws func setLocalTrust(userId: String, deviceId: String, trust: LocalTrust) throws @@ -61,7 +62,7 @@ protocol MXCryptoUserIdentitySource: MXCryptoIdentity { /// Event encryption and decryption protocol MXCryptoRoomEventEncrypting: MXCryptoIdentity { func shareRoomKeysIfNecessary(roomId: String, users: [String], settings: EncryptionSettings) async throws - func encryptRoomEvent(content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] + func encryptRoomEvent(content: [AnyHashable: Any], roomId: String, eventType: String) throws -> [String: Any] func decryptRoomEvent(_ event: MXEvent) -> MXEventDecryptionResult func requestRoomKey(event: MXEvent) async throws func discardRoomKey(roomId: String) diff --git a/MatrixSDK/Crypto/CryptoMachine/MXEventDecryptionResult+DecryptedEvent.swift b/MatrixSDK/Crypto/CryptoMachine/MXEventDecryptionResult+DecryptedEvent.swift index 024db4965e..3b61745d1e 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXEventDecryptionResult+DecryptedEvent.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXEventDecryptionResult+DecryptedEvent.swift @@ -37,6 +37,7 @@ extension MXEventDecryptionResult { senderCurve25519Key = event.senderCurve25519Key claimedEd25519Key = event.claimedEd25519Key forwardingCurve25519KeyChain = event.forwardingCurve25519Chain + isUntrusted = event.verificationState == VerificationState.untrusted } } diff --git a/MatrixSDK/Crypto/Devices/Data/MXCryptoDeviceWrapper.swift b/MatrixSDK/Crypto/Devices/Data/MXCryptoDeviceWrapper.swift index b526a2a91c..7b0ebc21b7 100644 --- a/MatrixSDK/Crypto/Devices/Data/MXCryptoDeviceWrapper.swift +++ b/MatrixSDK/Crypto/Devices/Data/MXCryptoDeviceWrapper.swift @@ -38,9 +38,13 @@ import MatrixSDKCrypto deviceId = device.deviceId algorithms = device.algorithms keys = device.keys - unsignedData = [ - "device_display_name": device.displayName as Any - ] + if let displayName = device.displayName { + unsignedData = [ + "device_display_name": displayName + ] + } else { + unsignedData = [:] + } let status: MXDeviceVerification if device.isBlocked { diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m index 46bc2a89a7..d171fc2d1e 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m @@ -104,7 +104,7 @@ - (void)checkAndStartKeyBackup } failure:^(NSError * _Nonnull error) { MXStrongifyAndReturnIfNil(self); - MXLogDebug(@"[MXKeyBackup] checkAndStartKeyBackup: Failed to get current version: %@", error); + MXLogErrorDetails(@"[MXKeyBackup] checkAndStartKeyBackup: Failed to get current version", error); self.state = MXKeyBackupStateUnknown; }]; } @@ -270,7 +270,7 @@ - (void)sendKeyBackup } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: backupRoomKeysSuccess failed. Error: %@", error); + MXLogErrorDetails(@"[MXKeyBackup] sendKeyBackup: backupRoomKeysSuccess failed", error); void (^backupAllGroupSessionsFailure)(NSError *error) = self->backupAllGroupSessionsFailure; @@ -304,7 +304,7 @@ - (void)requestPrivateKeys:(void (^)(void))onComplete [self restoreKeyBackupAutomaticallyWithPrivateKey:onComplete]; } failure:^(NSError * _Nonnull error) { - MXLogDebug(@"[MXKeyBackup] requestPrivateKeys. Error for requestPrivateKeys: %@", error); + MXLogErrorDetails(@"[MXKeyBackup] requestPrivateKeys. Error for requestPrivateKeys", error); onComplete(); }]; } @@ -323,15 +323,15 @@ - (void)restoreKeyBackupAutomaticallyWithPrivateKey:(void (^)(void))onComplete [self restoreKeyBackupAutomaticallyWithPrivateKey:onComplete]; } } failure:^(NSError * _Nonnull error) { - MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically: Cannot fetch backup version. Error: %@", error); + MXLogErrorDetails(@"[MXKeyBackup] restoreKeyBackupAutomatically: Cannot fetch backup version", error); }]; return; } // Check private keys - if (!self.engine.hasValidPrivateKey) + if (![self.engine hasValidPrivateKeyForKeyBackupVersion:self.keyBackupVersion]) { - MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error: No valid private key"); + MXLogError(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error: No valid private key"); onComplete(); return; } @@ -343,7 +343,7 @@ - (void)restoreKeyBackupAutomaticallyWithPrivateKey:(void (^)(void))onComplete onComplete(); } failure:^(NSError * _Nonnull error) { - MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error for restoreKeyBackup: %@", error); + MXLogErrorDetails(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error for restoreKeyBackup", error); onComplete(); }]; } diff --git a/MatrixSDK/Crypto/MXCrypto.h b/MatrixSDK/Crypto/MXCrypto.h index c9bb6c599e..65595dd5a9 100644 --- a/MatrixSDK/Crypto/MXCrypto.h +++ b/MatrixSDK/Crypto/MXCrypto.h @@ -79,6 +79,11 @@ extern NSString *const MXDeviceListDidUpdateUsersDevicesNotification; */ @protocol MXCrypto +/** + Version of the crypto module being used + */ +@property (nonatomic, readonly) NSString *version; + /** Curve25519 key for the account. */ @@ -205,7 +210,7 @@ extern NSString *const MXDeviceListDidUpdateUsersDevicesNotification; /** Handle the sync response that may contain crypto-related events */ -- (void)handleSyncResponse:(MXSyncResponse *)syncResponse; +- (void)handleSyncResponse:(MXSyncResponse *)syncResponse onComplete:(void (^)(void))onComplete; #pragma mark - Cross-signing / Local trust diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index 98ef747226..081928f92e 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -179,14 +179,24 @@ @implementation MXLegacyCrypto + (void)checkCryptoWithMatrixSession:(MXSession*)mxSession complete:(void (^)(id crypto))complete { #ifdef MX_CRYPTO - #if DEBUG id cryptoV2 = [self createCryptoV2IfAvailableWithSession:mxSession]; - if (cryptoV2) { + if (cryptoV2) + { complete(cryptoV2); return; } #endif + + [self checkLegacyCryptoWithMatrixSession:mxSession complete:complete]; +#else + complete(nil); +#endif +} + ++ (void)checkLegacyCryptoWithMatrixSession:(MXSession*)mxSession complete:(void (^)(id crypto))complete +{ +#ifdef MX_CRYPTO MXLogDebug(@"[MXCrypto] checkCryptoWithMatrixSession for %@", mxSession.matrixRestClient.credentials.userId); @@ -909,11 +919,12 @@ - (void)handleDeviceUnusedFallbackKeys:(NSArray *)deviceFallbackKeys #endif } -- (void)handleSyncResponse:(MXSyncResponse *)syncResponse +- (void)handleSyncResponse:(MXSyncResponse *)syncResponse onComplete:(void (^)(void))onComplete { // Not implemented, the default `MXCrypto` instead uses more specific functions // such as `handleRoomKeyEvent` and `handleDeviceUnusedFallbackKeys`. The method // is possibly used by `MXCrypto` subclasses. + onComplete(); } - (void)onSyncCompleted:(NSString *)oldSyncToken nextSyncToken:(NSString *)nextSyncToken catchingUp:(BOOL)catchingUp @@ -1404,6 +1415,11 @@ - (void)resetDeviceKeys #endif } +- (NSString *)version +{ + return [NSString stringWithFormat:@"OLM %@", self.olmVersion]; +} + - (NSString *)deviceCurve25519Key { #ifdef MX_CRYPTO diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index 4bf6f3cbf2..772d08b88b 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -74,10 +74,18 @@ private class MXCryptoV2: NSObject, MXCrypto { private let backupEngine: MXCryptoKeyBackupEngine? private let keyVerification: MXKeyVerificationManagerV2 private var undecryptableEvents = [String: MXEvent]() + private var roomEventObserver: Any? private let log = MXNamedLog(name: "MXCryptoV2") // MARK: - Public properties + var version: String { + guard let sdkVersion = Bundle(for: OlmMachine.self).infoDictionary?["CFBundleShortVersionString"] else { + return "Matrix SDK Crypto" + } + return "Matrix SDK Crypto \(sdkVersion)" + } + var deviceCurve25519Key: String? { return machine.deviceCurve25519Key } @@ -94,8 +102,9 @@ private class MXCryptoV2: NSObject, MXCrypto { init(session: MXSession) throws { guard let restClient = session.matrixRestClient, - let userId = session.credentials.userId, - let deviceId = session.credentials.deviceId + let credentials = session.credentials, + let userId = credentials.userId, + let deviceId = credentials.deviceId else { throw Error.missingCredentials } @@ -105,7 +114,11 @@ private class MXCryptoV2: NSObject, MXCrypto { // A few features (global untrusted users blacklist) are not yet implemented in `MatrixSDKCrypto` // so they have to be stored locally. Will be moved to `MatrixSDKCrypto` eventually - self.legacyStore = MXRealmCryptoStore(credentials: session.credentials) + if MXRealmCryptoStore.hasData(for: credentials) { + self.legacyStore = MXRealmCryptoStore(credentials: credentials) + } else { + self.legacyStore = MXRealmCryptoStore.createStore(with: credentials) + } machine = try MXCryptoMachine( userId: userId, @@ -169,6 +182,14 @@ private class MXCryptoV2: NSObject, MXCrypto { ) log.debug("Initialized Crypto module") + + super.init() + + listenToRoomEvents(in: session) + } + + deinit { + session?.removeListener(roomEventObserver) } // MARK: - Crypto start / close @@ -253,11 +274,10 @@ private class MXCryptoV2: NSObject, MXCrypto { users: users, settings: settings ) - let result = try await machine.encryptRoomEvent( + let result = try machine.encryptRoomEvent( content: eventContent, roomId: roomId, - eventType: eventType, - users: users + eventType: eventType ) let duration = Date().timeIntervalSince(startDate) * 1000 @@ -346,26 +366,56 @@ private class MXCryptoV2: NSObject, MXCrypto { // MARK: - Sync - public func handle(_ syncResponse: MXSyncResponse) { - do { - let toDevice = try machine.handleSyncResponse( - toDevice: syncResponse.toDevice, - deviceLists: syncResponse.deviceLists, - deviceOneTimeKeysCounts: syncResponse.deviceOneTimeKeysCount ?? [:], - unusedFallbackKeys: syncResponse.unusedFallbackKeys - ) - keyVerification.handleDeviceEvents(toDevice.events) - backup?.maybeSend() - } catch { - log.error("Cannot handle sync", context: error) - } + func handle(_ syncResponse: MXSyncResponse, onComplete: @escaping () -> Void) { + let uuid = UUID().uuidString + let toDeviceCount = syncResponse.toDevice?.events.count ?? 0 + + log.debug("Handling new sync response \(uuid), \(toDeviceCount) to-device events") Task { do { + let senders = syncResponse + .toDevice? + .events + .compactMap { $0.sender } + .filter { $0 != machine.userId } ?? [] + + try await machine.updateTrackedUsers(users: senders) + try await handle(syncResponse: syncResponse) try await machine.processOutgoingRequests() } catch { - log.error("Error processing outgoing requests", context: error) + log.error("Cannot handle sync", context: error) } + + log.debug("Completing sync response \(uuid)") + await MainActor.run { + onComplete() + } + } + } + + @MainActor + private func handle(syncResponse: MXSyncResponse) async throws { + let toDevice = try machine.handleSyncResponse( + toDevice: syncResponse.toDevice, + deviceLists: syncResponse.deviceLists, + deviceOneTimeKeysCounts: syncResponse.deviceOneTimeKeysCount ?? [:], + unusedFallbackKeys: syncResponse.unusedFallbackKeys + ) + + // Some of the to-device events processed by the machine require further updates + // on the client side, not currently exposed through any convenient api. + // These include new key verification events, or receiving backup key + // which allows downloading room keys from backup. + for event in toDevice.events { + keyVerification.handleDeviceEvent(event) + restoreBackupIfPossible(event: event) + } + + backup?.maybeSend() + + if !toDevice.events.isEmpty { + retryUndecryptableEvents() } } @@ -488,7 +538,7 @@ private class MXCryptoV2: NSObject, MXCrypto { Task { do { - try await machine.downloadKeys(users: userIds) + try await machine.updateTrackedUsers(users: userIds) log.debug("Downloaded keys") await MainActor.run { @@ -623,6 +673,20 @@ private class MXCryptoV2: NSObject, MXCrypto { // MARK: - Private + private func listenToRoomEvents(in session: MXSession) { + roomEventObserver = session.listenToEvents(Array(MXKeyVerificationManagerV2.dmEventTypes)) { [weak self] event, direction, _ in + guard let self = self else { return } + + if direction == .forwards && event.sender != session.myUserId { + Task { + try await self.machine.updateTrackedUsers(users: [event.sender]) + await self.keyVerification.handleRoomEvent(event) + try await self.machine.processOutgoingRequests() + } + } + } + } + private func decrypt(event: MXEvent) -> MXEventDecryptionResult { guard event.isEncrypted && event.content?["algorithm"] as? String == kMXCryptoMegolmAlgorithm else { log.debug("Ignoring non-room event") @@ -636,6 +700,34 @@ private class MXCryptoV2: NSObject, MXCrypto { return result } + private func restoreBackupIfPossible(event: MXEvent) { + guard + event.type == kMXEventTypeStringSecretSend + && event.content?["name"] as? NSString == MXSecretId.keyBackup.takeUnretainedValue(), + let secret = MXSecretShareSend(fromJSON: event.content)?.secret + else { + return + } + + log.debug("Restoring backup after receiving backup key") + + guard + let backupVersion = backup?.keyBackupVersion, + let version = backupVersion.version else + { + log.error("There is not backup version to restore") + return + } + + let data = MXBase64Tools.data(fromBase64: secret) + backupEngine?.savePrivateKey(data, version: version) + + log.debug("Restoring room keys") + backup?.restore(usingPrivateKeyKeyBackup: backupVersion, room: nil, session: nil) { [weak self] total, imported in + self?.log.debug("Restored \(imported) out of \(total) room keys") + } + } + private func retryUndecryptableEvents() { for (eventId, event) in undecryptableEvents { let result = decrypt(event: event) diff --git a/MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift b/MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift index bdaef815d3..a1a6cd59da 100644 --- a/MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift +++ b/MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift @@ -34,6 +34,8 @@ class MXCryptoSecretStoreV2: NSObject, MXCryptoSecretStore { } func storeSecret(_ secret: String, withSecretId secretId: String) { + log.debug("Storing new secret \(secretId)") + switch secretId as NSString { case MXSecretId.crossSigningMaster.takeUnretainedValue(): crossSigning.importCrossSigningKeys( diff --git a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift index a83d919e12..9bc20acb95 100644 --- a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift +++ b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift @@ -38,7 +38,7 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { // A set of room events we have to monitor manually to synchronize CryptoMachine // and verification UI, optionally triggering global notifications. - private static let dmEventTypes: Set = [ + static let dmEventTypes: Set = [ .roomMessage, // Verification request in DM is wrapped inside `m.room.message` .keyVerificationReady, .keyVerificationStart, @@ -57,8 +57,6 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { ] private weak var session: MXSession? - private var observer: Any? - private let handler: MXCryptoVerificationHandler // We need to keep track of request / transaction objects by reference @@ -79,14 +77,6 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { self.activeRequests = [:] self.activeTransactions = [:] self.resolver = MXKeyVerificationStateResolver(myUserId: session.myUserId, aggregations: session.aggregations) - - super.init() - - listenToRoomEvents(in: session) - } - - deinit { - session?.removeListener(observer) } var pendingRequests: [MXKeyVerificationRequest] { @@ -269,25 +259,15 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { // MARK: - Events - func handleDeviceEvents(_ events: [MXEvent]) { - for event in events { - guard Self.toDeviceEventTypes.contains(event.type) else { - continue - } - handleDeviceEvent(event) - } - updatePendingVerification() - } - - private func listenToRoomEvents(in session: MXSession) { - observer = session.listenToEvents(Array(Self.dmEventTypes)) { [weak self] event, direction, customObject in - if direction == .forwards && event.sender != session.myUserId { - self?.handleRoomEvent(event) - } + @MainActor + func handleDeviceEvent(_ event: MXEvent) { + guard Self.toDeviceEventTypes.contains(event.type) else { + updatePendingVerification() + return } - } - - private func handleDeviceEvent(_ event: MXEvent) { + + log.debug("->") + guard let userId = event.sender, let flowId = event.content["transaction_id"] as? String @@ -296,8 +276,6 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { return } - log.debug("->") - switch event.type { case kMXMessageTypeKeyVerificationRequest: handleIncomingRequest(userId: userId, flowId: flowId, transport: .toDevice) @@ -308,9 +286,16 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { default: log.failure("Event type should not be handled by key verification", context: event.type) } + + updatePendingVerification() } - private func handleRoomEvent(_ event: MXEvent) { + @MainActor + func handleRoomEvent(_ event: MXEvent) { + guard Self.dmEventTypes.contains(where: { $0.identifier == event.type }) else { + return + } + log.debug("->") if !event.isEncrypted, let roomId = event.roomId { @@ -322,17 +307,14 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { } else if event.type == kMXEventTypeStringKeyVerificationStart, let flowId = event.relatesTo.eventId { handleIncomingVerification(userId: event.sender, flowId: flowId, transport: .directMessage) - - } else if Self.dmEventTypes.contains(where: { $0.identifier == event.type }) { - updatePendingVerification() - - } else if event.type != kMXEventTypeStringRoomMessage { - log.failure("Event type should not be handled by key verification", context: event.type) } + + updatePendingVerification() } // MARK: - Update + @MainActor func updatePendingVerification() { if !activeRequests.isEmpty { log.debug("Processing \(activeRequests.count) pending requests") @@ -374,6 +356,8 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { deviceIds: [String]?, methods: [String] ) async throws -> MXKeyVerificationRequest { + log.debug("->") + if userId == session?.myUserId { log.debug("Self-verification") return try await requestSelfVerification(methods: methods) @@ -412,7 +396,7 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { private func requestSelfVerification(methods: [String]) async throws -> MXKeyVerificationRequest { log.debug("->") - + let request = try await handler.requestSelfVerification(methods: methods) return addRequest(for: request, transport: .directMessage) } @@ -456,6 +440,10 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { MXKeyVerificationManagerNotificationRequestKey: request ] ) + NotificationCenter.default.post( + name: .MXKeyVerificationRequestDidChange, + object: request + ) } return request } @@ -481,14 +469,15 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { switch verification { case .sasV1(let sas): log.debug("Tracking new SAS verification transaction") - let transaction = addSasTransaction(for: sas, transport: transport) - transaction.accept() + _ = addSasTransaction(for: sas, transport: transport, notify: true) case .qrCodeV1(let qrCode): if activeTransactions[flowId] is MXQRCodeTransaction { // This flow may happen if we have previously started a QR verification, but so has the other side, // and we scanned their code which now takes over the verification flow log.debug("Updating existing QR verification transaction") - updatePendingVerification() + Task { + await updatePendingVerification() + } } else { log.debug("Tracking new QR verification transaction") _ = addQrTransaction(for: qrCode, transport: transport) @@ -498,7 +487,8 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { private func addSasTransaction( for sas: Sas, - transport: MXKeyVerificationTransport + transport: MXKeyVerificationTransport, + notify: Bool = false ) -> MXSASTransactionV2 { let transaction = MXSASTransactionV2( sas: sas, @@ -506,6 +496,19 @@ class MXKeyVerificationManagerV2: NSObject, MXKeyVerificationManager { handler: handler ) activeTransactions[transaction.transactionId] = transaction + if notify { + NotificationCenter.default.post( + name: .MXKeyVerificationManagerNewTransaction, + object: self, + userInfo: [ + MXKeyVerificationManagerNotificationTransactionKey: transaction + ] + ) + NotificationCenter.default.post( + name: .MXKeyVerificationTransactionDidChange, + object: transaction + ) + } return transaction } diff --git a/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift b/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift index 1fd2151054..3c03f967c3 100644 --- a/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift +++ b/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift @@ -122,6 +122,8 @@ class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { success: @escaping () -> Void, failure: @escaping (Error) -> Void ) { + log.debug("->") + Task { do { try await handler.acceptVerificationRequest( @@ -147,6 +149,8 @@ class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { success: (() -> Void)?, failure: ((Error) -> Void)? = nil ) { + log.debug("->") + Task { do { try await handler.cancelVerification( diff --git a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift index f2a47899ce..d496975ae8 100644 --- a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift +++ b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift @@ -32,8 +32,12 @@ class MXSASTransactionV2: NSObject, MXSASTransaction { return sas.cancelInfo?.cancelledByUs == true ? MXSASTransactionStateCancelledByMe : MXSASTransactionStateCancelled } else if sas.canBePresented { return MXSASTransactionStateShowSAS + } else if sas.weStarted { + return MXSASTransactionStateOutgoingWaitForPartnerToAccept + } else if !sas.hasBeenAccepted { + return MXSASTransactionStateIncomingShowAccept } - return sas.weStarted ? MXSASTransactionStateOutgoingWaitForPartnerToAccept : MXSASTransactionStateIncomingShowAccept + return MXSASTransactionStateUnknown } var sasEmoji: [MXEmojiRepresentation]? { @@ -113,6 +117,8 @@ class MXSASTransactionV2: NSObject, MXSASTransaction { } func accept() { + log.debug("->") + Task { do { try await handler.acceptSasVerification(userId: otherUserId, flowId: transactionId) @@ -124,6 +130,8 @@ class MXSASTransactionV2: NSObject, MXSASTransaction { } func confirmSASMatch() { + log.debug("->") + Task { do { try await handler.confirmVerification(userId: otherUserId, flowId: transactionId) diff --git a/MatrixSDK/JSONModels/MXJSONModels.m b/MatrixSDK/JSONModels/MXJSONModels.m index 12ac462f1c..75ddf0a646 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.m +++ b/MatrixSDK/JSONModels/MXJSONModels.m @@ -1283,7 +1283,12 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary - (NSDictionary *)JSONDictionary { - return self.responseJSON; + NSMutableDictionary *dictionary = [self.responseJSON mutableCopy]; + if (!dictionary[@"failures"]) + { + dictionary[@"failures"] = @{}; + } + return dictionary.copy; } @end diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index c79945a210..54984b3fcc 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -1925,6 +1925,11 @@ - (void)handleAccountData:(NSDictionary*)accountDataUpdate */ - (void)validateAccountData { + if (![self.crypto isKindOfClass:[MXLegacyCrypto class]]) + { + return; + } + // Detecting an issue in legacy crypto where more than one valid SSSS key is present on the client // https://github.com/vector-im/element-ios/issues/4569 NSInteger keysCount = ((MXLegacyCrypto *)self.crypto).secretStorage.numberOfValidKeys; @@ -1974,8 +1979,7 @@ - (void)handleCryptoSyncResponse:(MXSyncResponse *)syncResponse else { // New and all future crypto modules can handle the entire sync response in full - [self.crypto handleSyncResponse:syncResponse]; - onComplete(); + [self.crypto handleSyncResponse:syncResponse onComplete:onComplete]; } } @@ -2036,7 +2040,10 @@ - (void)handleToDeviceEvent:(MXEvent *)event onComplete:(void (^)(void))onComple { case MXEventTypeRoomKey: { - [(MXLegacyCrypto *)_crypto handleRoomKeyEvent:event onComplete:onHandleToDeviceEventDone]; + if ([_crypto isKindOfClass:[MXLegacyCrypto class]]) + { + [(MXLegacyCrypto *)_crypto handleRoomKeyEvent:event onComplete:onHandleToDeviceEventDone]; + } break; } diff --git a/MatrixSDK/MatrixSDK.h b/MatrixSDK/MatrixSDK.h index 67b331af2f..799b21c13b 100644 --- a/MatrixSDK/MatrixSDK.h +++ b/MatrixSDK/MatrixSDK.h @@ -176,6 +176,7 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXKeyBackupEngine.h" #import "MXCryptoTools.h" #import "MXRecoveryKey.h" +#import "MXSecretShareSend.h" // Sync response models #import "MXSyncResponse.h" diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift new file mode 100644 index 0000000000..c1984ba5d6 --- /dev/null +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift @@ -0,0 +1,69 @@ +// +// Copyright 2022 The Matrix.org Foundation C.I.C +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +#if DEBUG + +import MatrixSDKCrypto +@testable import MatrixSDK + +class MXCryptoMachineUnitTests: XCTestCase { + + var restClient: MXRestClient! + var machine: MXCryptoMachine! + + override func setUp() { + restClient = MXRestClientStub() + machine = try! MXCryptoMachine( + userId: "@alice:matrix.org", + deviceId: "ABCD", + restClient: restClient, + getRoomAction: { + MXRoom(roomId: $0, andMatrixSession: nil) + }) + } + + func test_handleSyncResponse_canProcessEmptyResponse() throws { + let result = try machine.handleSyncResponse( + toDevice: nil, + deviceLists: nil, + deviceOneTimeKeysCounts: [:], + unusedFallbackKeys: nil + ) + XCTAssertEqual(result.events.count, 0) + } + + func test_handleSyncResponse_canProcessToDeviceEvents() async throws { + let toDevice = MXToDeviceSyncResponse() + toDevice.events = [ + .fixture(type: "m.key.verification.request") + ] + let deviceList = MXDeviceListResponse() + deviceList.changed = ["A", "B"] + deviceList.left = ["C", "D"] + + let result = try machine.handleSyncResponse( + toDevice: toDevice, + deviceLists: deviceList, + deviceOneTimeKeysCounts: [:], + unusedFallbackKeys: nil + ) + XCTAssertEqual(result.events.count, 1) + } +} + +#endif diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift index 3e7583f5e5..724d399abf 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift @@ -57,7 +57,11 @@ class UserIdentitySourceStub: CryptoIdentityStub, MXCryptoUserIdentitySource { return verification[userId] ?? false } - func downloadKeys(users: [String]) async throws { + func isUserTracked(userId: String) -> Bool { + return false + } + + func updateTrackedUsers(users: [String]) async throws { } func manuallyVerifyUser(userId: String) async throws { @@ -100,7 +104,11 @@ class CryptoCrossSigningStub: CryptoIdentityStub, MXCryptoCrossSigning { return stubbedVerifiedUsers.contains(userId) } - func downloadKeys(users: [String]) async throws { + func isUserTracked(userId: String) -> Bool { + return false + } + + func updateTrackedUsers(users: [String]) async throws { } func manuallyVerifyUser(userId: String) async throws { diff --git a/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift b/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift index c25e5adaa8..8012a738ab 100644 --- a/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift +++ b/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift @@ -135,6 +135,14 @@ class MXSASTransactionV2UnitTests: XCTestCase { isDone: false, isCancelled: false ), MXSASTransactionStateIncomingShowAccept), + (.stub( + weStarted: false, + hasBeenAccepted: true, + canBePresented: false, + haveWeConfirmed: false, + isDone: false, + isCancelled: false + ), MXSASTransactionStateUnknown), ] for (stub, state) in testCases { diff --git a/Podfile b/Podfile index a28fa6d6f3..1cef46e9a2 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ abstract_target 'MatrixSDK' do pod 'Realm', '10.27.0' pod 'libbase58', '~> 0.1.4' - pod 'MatrixSDKCrypto', "0.1.4", :configurations => ['DEBUG'] + pod 'MatrixSDKCrypto', "0.1.5", :configurations => ['DEBUG'] target 'MatrixSDK-iOS' do platform :ios, '11.0' diff --git a/Podfile.lock b/Podfile.lock index 250682f6e8..13e47dbf52 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -16,7 +16,7 @@ PODS: - AFNetworking/NSURLSession - GZIP (1.3.0) - libbase58 (0.1.4) - - MatrixSDKCrypto (0.1.4) + - MatrixSDKCrypto (0.1.5) - OHHTTPStubs (9.1.0): - OHHTTPStubs/Default (= 9.1.0) - OHHTTPStubs/Core (9.1.0) @@ -44,7 +44,7 @@ DEPENDENCIES: - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - - MatrixSDKCrypto (= 0.1.4) + - MatrixSDKCrypto (= 0.1.5) - OHHTTPStubs (~> 9.1.0) - OLMKit (~> 3.2.5) - Realm (= 10.27.0) @@ -65,12 +65,12 @@ SPEC CHECKSUMS: AFNetworking: 7864c38297c79aaca1500c33288e429c3451fdce GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3 libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd - MatrixSDKCrypto: 2cefddf230a2388fef10cb3249070cc2dc9b1688 + MatrixSDKCrypto: dcab554bc7157cad31c01fc1137cf5acb01959a4 OHHTTPStubs: 90eac6d8f2c18317baeca36698523dc67c513831 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 Realm: 9ca328bd7e700cc19703799785e37f77d1a130f2 SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 -PODFILE CHECKSUM: 6d6b363a8a51d2a0ecce801a900d2d2e506f93c8 +PODFILE CHECKSUM: 7805b1fe65269b6ac6667a7f347f324e8970c050 -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3