diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index b48b49624a..10052ebebd 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1874,6 +1874,14 @@ ED51943A28462D130006EEC6 /* MXRoomStateUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED51943828462D130006EEC6 /* MXRoomStateUnitTests.swift */; }; ED51943C284630090006EEC6 /* MXRestClientStub.m in Sources */ = {isa = PBXBuildFile; fileRef = ED51943B284630090006EEC6 /* MXRestClientStub.m */; }; ED51943D284630090006EEC6 /* MXRestClientStub.m in Sources */ = {isa = PBXBuildFile; fileRef = ED51943B284630090006EEC6 /* MXRestClientStub.m */; }; + ED558068296F0361003443E3 /* MXCryptoMigrationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED558067296F0361003443E3 /* MXCryptoMigrationStore.swift */; }; + ED558069296F0361003443E3 /* MXCryptoMigrationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED558067296F0361003443E3 /* MXCryptoMigrationStore.swift */; }; + ED55806D296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED55806C296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift */; }; + ED55806E296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED55806C296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift */; }; + ED558070296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED55806F296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift */; }; + ED558071296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED55806F296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift */; }; + ED5580732970265A003443E3 /* MXCryptoMachineLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5580722970265A003443E3 /* MXCryptoMachineLogger.swift */; }; + ED5580742970265A003443E3 /* MXCryptoMachineLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5580722970265A003443E3 /* MXCryptoMachineLogger.swift */; }; ED5AE8C52816C8CF00105072 /* MXCoreDataRoomSummaryStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = ED5AE8C22816C8CF00105072 /* MXCoreDataRoomSummaryStore.xcdatamodeld */; }; ED5AE8C62816C8CF00105072 /* MXCoreDataRoomSummaryStore.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = ED5AE8C22816C8CF00105072 /* MXCoreDataRoomSummaryStore.xcdatamodeld */; }; ED5C753C28B3E80300D24E85 /* MXLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = ED5C753528B3E80300D24E85 /* MXLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -2001,6 +2009,8 @@ EDC8C40E2968C37F003792C5 /* MXKeysQuerySchedulerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDC8C40A2968A9F7003792C5 /* MXKeysQuerySchedulerUnitTests.swift */; }; EDCB65E22912AB0C00F55D4D /* MXRoomEventDecryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDCB65E12912AB0C00F55D4D /* MXRoomEventDecryption.swift */; }; EDCB65E32912AB0C00F55D4D /* MXRoomEventDecryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDCB65E12912AB0C00F55D4D /* MXRoomEventDecryption.swift */; }; + EDCF802D2941FF220059E774 /* MXCryptoMigrationV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDCF802C2941FF220059E774 /* MXCryptoMigrationV2.swift */; }; + EDCF802E2941FF220059E774 /* MXCryptoMigrationV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDCF802C2941FF220059E774 /* MXCryptoMigrationV2.swift */; }; EDD4197E28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */; }; EDD4197F28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */; }; EDD4198128DCAA7B007F3757 /* MXNativeKeyBackupEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */; }; @@ -3065,6 +3075,10 @@ ED51943828462D130006EEC6 /* MXRoomStateUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomStateUnitTests.swift; sourceTree = ""; }; ED51943B284630090006EEC6 /* MXRestClientStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRestClientStub.m; sourceTree = ""; }; ED51943E284630100006EEC6 /* MXRestClientStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRestClientStub.h; sourceTree = ""; }; + ED558067296F0361003443E3 /* MXCryptoMigrationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoMigrationStore.swift; sourceTree = ""; }; + ED55806C296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoMigrationStoreUnitTests.swift; sourceTree = ""; }; + ED55806F296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoMigrationV2Tests.swift; sourceTree = ""; }; + ED5580722970265A003443E3 /* MXCryptoMachineLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoMachineLogger.swift; sourceTree = ""; }; ED5AE8C32816C8CF00105072 /* MXRoomSummaryCoreDataStore2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MXRoomSummaryCoreDataStore2.xcdatamodel; sourceTree = ""; }; ED5AE8C42816C8CF00105072 /* MXRoomSummaryCoreDataStore.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MXRoomSummaryCoreDataStore.xcdatamodel; sourceTree = ""; }; ED5C753528B3E80300D24E85 /* MXLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXLogger.h; sourceTree = ""; }; @@ -3127,6 +3141,7 @@ EDC8C4072968A993003792C5 /* MXKeysQueryScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXKeysQueryScheduler.swift; sourceTree = ""; }; EDC8C40A2968A9F7003792C5 /* MXKeysQuerySchedulerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXKeysQuerySchedulerUnitTests.swift; sourceTree = ""; }; EDCB65E12912AB0C00F55D4D /* MXRoomEventDecryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomEventDecryption.swift; sourceTree = ""; }; + EDCF802C2941FF220059E774 /* MXCryptoMigrationV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoMigrationV2.swift; sourceTree = ""; }; EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXNativeKeyBackupEngine.h; sourceTree = ""; }; EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXNativeKeyBackupEngine.m; sourceTree = ""; }; EDD578DC2881C37C006739DD /* MXDeviceInfoSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXDeviceInfoSource.swift; sourceTree = ""; }; @@ -4401,9 +4416,11 @@ 32C78B64256CFC4D008130B1 /* Migration */ = { isa = PBXGroup; children = ( + ED558066296F034F003443E3 /* Data */, 32C78B65256CFC4D008130B1 /* MXCryptoVersion.h */, 32C78B66256CFC4D008130B1 /* MXCryptoMigration.m */, 32C78B67256CFC4D008130B1 /* MXCryptoMigration.h */, + EDCF802C2941FF220059E774 /* MXCryptoMigrationV2.swift */, ); path = Migration; sourceTree = ""; @@ -5350,6 +5367,7 @@ ED21F67A28104B9A002FF83D /* Crypto */ = { isa = PBXGroup; children = ( + ED55806A296F0E18003443E3 /* Migration */, ED8F1D1428857FD300F897E7 /* CrossSigning */, ED8F1D2E2885AAEB00F897E7 /* Trust */, ED8F1D292885A7DF00F897E7 /* Devices */, @@ -5398,6 +5416,7 @@ isa = PBXGroup; children = ( ED2DD111286C450600F06731 /* MXCryptoMachine.swift */, + ED5580722970265A003443E3 /* MXCryptoMachineLogger.swift */, ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */, ED2DD112286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift */, ED2DD113286C450600F06731 /* MXCryptoRequests.swift */, @@ -5475,6 +5494,31 @@ path = Data; sourceTree = ""; }; + ED558066296F034F003443E3 /* Data */ = { + isa = PBXGroup; + children = ( + ED558067296F0361003443E3 /* MXCryptoMigrationStore.swift */, + ); + path = Data; + sourceTree = ""; + }; + ED55806A296F0E18003443E3 /* Migration */ = { + isa = PBXGroup; + children = ( + ED55806B296F0E1D003443E3 /* Data */, + ED55806F296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift */, + ); + path = Migration; + sourceTree = ""; + }; + ED55806B296F0E1D003443E3 /* Data */ = { + isa = PBXGroup; + children = ( + ED55806C296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift */, + ); + path = Data; + sourceTree = ""; + }; ED5C753428B3E80300D24E85 /* Logs */ = { isa = PBXGroup; children = ( @@ -6801,6 +6845,7 @@ 32999DE022DCD183004FF987 /* MXPusher.m in Sources */, F03EF4FF1DF014D9009DF592 /* MXMediaLoader.m in Sources */, ECDA763227B293D9000C48CF /* MXThreadProtocol.swift in Sources */, + EDCF802D2941FF220059E774 /* MXCryptoMigrationV2.swift in Sources */, 320A8841217F4E3F002EA952 /* MXCurve25519BackupAuthData.m in Sources */, B1A0270226162110001AADFF /* MXSpaceChildrenResponse.m in Sources */, 66398BA527A4085B00466E89 /* MXRefreshResponse.m in Sources */, @@ -6952,6 +6997,7 @@ ED647E3E292CE64400A47519 /* MXSessionSyncProgress.swift in Sources */, EC8A53C525B1BC77004E0802 /* MXTurnServerResponse.m in Sources */, 32A151271DABB0CB00400192 /* MXMegolmDecryption.m in Sources */, + ED558068296F0361003443E3 /* MXCryptoMigrationStore.swift in Sources */, 18121F78273E6E1E00B68ADF /* PollBuilder.swift in Sources */, 327A5F50239805F600ED6329 /* MXKeyVerificationKey.m in Sources */, B16C56E2261D0A9D00604765 /* MXSpaceChildInfo.swift in Sources */, @@ -7234,6 +7280,7 @@ B1F04B132811E9D300103EBE /* MXBeaconInfoSummaryStoreProtocol.swift in Sources */, 183892802702F553003F0C4F /* MXRoomNameDefaultStringLocalizer.m in Sources */, C6F9358B1E5B3BE600FC34BF /* MXJSONModels.swift in Sources */, + ED5580732970265A003443E3 /* MXCryptoMachineLogger.swift in Sources */, EC8A539725B1BC77004E0802 /* MXCallReplacesEventContent.m in Sources */, B135066E27EA44C800BD3276 /* MXLocationServiceError.swift in Sources */, 32FA10C21FA1C9EE00E54233 /* MXOutgoingRoomKeyRequestManager.m in Sources */, @@ -7271,6 +7318,7 @@ 18121F75273E6D2400B68ADF /* MXPollBuilderTests.swift in Sources */, ED7019F72886CA6C00FC31B9 /* VerificationRequestStub.swift in Sources */, B14EECEE2578FE3F00448735 /* MXAuthenticationSessionUnitTests.swift in Sources */, + ED558070296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift in Sources */, ED2DD11D286C4F4400F06731 /* MXCryptoRequestsUnitTests.swift in Sources */, 32832B5D1BCC048300241108 /* MXStoreMemoryStoreTests.m in Sources */, EDB4209927DF842F0036AF39 /* MXEventFixtures.swift in Sources */, @@ -7349,6 +7397,7 @@ ED7019FB2886CA6C00FC31B9 /* MXSASTransactionV2UnitTests.swift in Sources */, 32B0E3E723A3864C0054FF1A /* MXEventReferenceUnitTests.swift in Sources */, 32720DA2222EB5650086FFF5 /* MXAutoDiscoveryTests.m in Sources */, + ED55806D296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift in Sources */, ED8943D427E34762000FC39C /* MXMemoryRoomStoreUnitTests.swift in Sources */, ED5C95CE2833E85600843D82 /* MXOlmDeviceUnitTests.swift in Sources */, 327E37B91A977810007F026F /* MXLoggerUnitTests.m in Sources */, @@ -7452,6 +7501,7 @@ ECDA763327B293D9000C48CF /* MXThreadProtocol.swift in Sources */, B14EF1E02397E90400758AF0 /* MXRealmCryptoStore.m in Sources */, B14EF1E12397E90400758AF0 /* MXRoomSummary.m in Sources */, + EDCF802E2941FF220059E774 /* MXCryptoMigrationV2.swift in Sources */, 66398BA627A4085B00466E89 /* MXRefreshResponse.m in Sources */, B14EF1E22397E90400758AF0 /* MXPushRuleRoomMemberCountConditionChecker.m in Sources */, B14EF1E32397E90400758AF0 /* MXCall.m in Sources */, @@ -7603,6 +7653,7 @@ ED647E3F292CE64400A47519 /* MXSessionSyncProgress.swift in Sources */, B14EF2212397E90400758AF0 /* MX3PID.swift in Sources */, 18121F79273E6E4100B68ADF /* PollBuilder.swift in Sources */, + ED558069296F0361003443E3 /* MXCryptoMigrationStore.swift in Sources */, EC383BB325406892002FBBE6 /* MXSyncResponseStore.swift in Sources */, ECBF658826DE3DF800AA3A99 /* MXFileRoomOutgoingMessagesStore.m in Sources */, B14EF2222397E90400758AF0 /* MXMediaScan.m in Sources */, @@ -7885,6 +7936,7 @@ B1F04B142811E9D300103EBE /* MXBeaconInfoSummaryStoreProtocol.swift in Sources */, EC8A53A225B1BC77004E0802 /* MXCallSelectAnswerEventContent.m in Sources */, 183892812702F553003F0C4F /* MXRoomNameDefaultStringLocalizer.m in Sources */, + ED5580742970265A003443E3 /* MXCryptoMachineLogger.swift in Sources */, B14EF2912397E90400758AF0 /* MXOutgoingRoomKeyRequestManager.m in Sources */, B14EF2922397E90400758AF0 /* MXWellKnown.m in Sources */, B1A026F926161EF5001AADFF /* MXSpaceChildSummaryResponse.m in Sources */, @@ -7922,6 +7974,7 @@ B1E09A442397FD940057C069 /* Dummy.swift in Sources */, ED7019F82886CA6C00FC31B9 /* VerificationRequestStub.swift in Sources */, 18121F76273E6D2400B68ADF /* MXPollBuilderTests.swift in Sources */, + ED558071296F1BEE003443E3 /* MXCryptoMigrationV2Tests.swift in Sources */, B1E09A1A2397FCE90057C069 /* MXAggregatedEditsTests.m in Sources */, B1E09A1F2397FCE90057C069 /* MXAutoDiscoveryTests.m in Sources */, EDB4209A27DF842F0036AF39 /* MXEventFixtures.swift in Sources */, @@ -8000,6 +8053,7 @@ 32B4778F2638133D00EA5800 /* MXJSONModelUnitTests.m in Sources */, B1F939F626289F2600D0E525 /* MXSpaceChildContentTests.swift in Sources */, EC40386828A279220067D5B8 /* MXKeyBackupUnitTests.swift in Sources */, + ED55806E296F0E3A003443E3 /* MXCryptoMigrationStoreUnitTests.swift in Sources */, B1E09A412397FD820057C069 /* MXAccountDataTests.m in Sources */, B1E09A2D2397FD750057C069 /* MXRestClientNoAuthAPITests.m in Sources */, ED8943D527E34762000FC39C /* MXMemoryRoomStoreUnitTests.swift in Sources */, diff --git a/MatrixSDK/Background/MXBackgroundCryptoStore.m b/MatrixSDK/Background/MXBackgroundCryptoStore.m index d1f49f9d14..cdeb1324f3 100644 --- a/MatrixSDK/Background/MXBackgroundCryptoStore.m +++ b/MatrixSDK/Background/MXBackgroundCryptoStore.m @@ -180,7 +180,7 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS if (olmSession) { MXLogDebug(@"[MXBackgroundCryptoStore] performSessionOperationWithDevice: Transfer data for %@ from cryptoStore to bgCryptoStore", sessionId); - [bgCryptoStore storeSession:olmSession forDevice:deviceKey]; + [bgCryptoStore storeSession:olmSession]; } } @@ -209,9 +209,21 @@ - (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*) return sessions; } -- (void)storeSession:(MXOlmSession*)session forDevice:(NSString*)deviceKey +- (NSArray *)sessions { - [bgCryptoStore storeSession:session forDevice:deviceKey]; + NSArray *bgSessions = [bgCryptoStore sessions] ?: @[]; + NSArray *appSessions = [cryptoStore sessions] ?: @[]; + + NSMutableArray *sessions = [NSMutableArray array]; + [sessions addObjectsFromArray:bgSessions]; + [sessions addObjectsFromArray:appSessions]; + + return sessions; +} + +- (void)storeSession:(MXOlmSession*)session +{ + [bgCryptoStore storeSession:session]; } @@ -322,6 +334,12 @@ + (void)deleteReadonlyStoreWithCredentials:(MXCredentials*)credentials NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); } +- (NSString *)userId +{ + NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); + return nil; +} + - (void)storeDeviceId:(NSString*)deviceId { NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index d180fb7100..328213415e 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -80,6 +80,7 @@ class MXCryptoMachine { getRoomAction: @escaping GetRoomAction ) throws { let url = try Self.storeURL(for: userId) + machine = try OlmMachine( userId: userId, deviceId: deviceId, @@ -93,8 +94,6 @@ class MXCryptoMachine { try await requests.queryKeys(users: users) } self.getRoomAction = getRoomAction - - setLogger(logger: self) } func start() async throws { @@ -751,10 +750,4 @@ extension MXCryptoMachine: MXCryptoBackup { } } -extension MXCryptoMachine: Logger { - func log(logLine: String) { - MXLog.debug("[MXCryptoMachine] \(logLine)") - } -} - #endif diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachineLogger.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachineLogger.swift new file mode 100644 index 0000000000..75ff8f6389 --- /dev/null +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachineLogger.swift @@ -0,0 +1,40 @@ +// +// Copyright 2023 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 + +/// Redirects logs originating in `MatrixSDKCrypto` into `MXLog` +class MXCryptoMachineLogger: Logger { + init() { + setLogger(logger: self) + } + + func log(logLine: String) { + // Excluding some auto-generated logs that are not useful + // This will be changed in rust-sdk directly + guard !logLine.contains("::uniffi_api:") else { + return + } + + MXLog.debug("[MXCryptoMachine] \(logLine)") + } +} + +#endif diff --git a/MatrixSDK/Crypto/Data/MXOlmSession.h b/MatrixSDK/Crypto/Data/MXOlmSession.h index 9fff0ecc5b..3cb45ee9a0 100644 --- a/MatrixSDK/Crypto/Data/MXOlmSession.h +++ b/MatrixSDK/Crypto/Data/MXOlmSession.h @@ -30,7 +30,13 @@ NS_ASSUME_NONNULL_BEGIN @interface MXOlmSession : NSObject -- (instancetype)initWithOlmSession:(OLMSession*)session; +- (instancetype)initWithOlmSession:(OLMSession*)session + deviceKey:(NSString*)deviceKey; + +/** + The curve25519 key of the other user that we share this session with. + */ +@property (nonatomic, readonly) NSString *deviceKey; /** The associated olm session. diff --git a/MatrixSDK/Crypto/Data/MXOlmSession.m b/MatrixSDK/Crypto/Data/MXOlmSession.m index 58bffecf89..4b4c0a867d 100644 --- a/MatrixSDK/Crypto/Data/MXOlmSession.m +++ b/MatrixSDK/Crypto/Data/MXOlmSession.m @@ -19,12 +19,13 @@ @implementation MXOlmSession -- (instancetype)initWithOlmSession:(OLMSession *)session +- (instancetype)initWithOlmSession:(OLMSession *)session deviceKey:(NSString *)deviceKey { self = [super init]; if (self) { _session = session; + _deviceKey = deviceKey; _lastReceivedMessageTs = 0; } return self; diff --git a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h index b038492382..5741516cc1 100644 --- a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h +++ b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h @@ -94,6 +94,11 @@ */ - (void)open:(void (^)(void))onComplete failure:(void (^)(NSError *error))failure; +/** + The user id. + */ +- (NSString*)userId; + /** Store the device id. */ @@ -256,10 +261,9 @@ /** Store a session between this device and another device. - @param deviceKey the public key of the other device. @param session the end-to-end session. */ -- (void)storeSession:(MXOlmSession*)session forDevice:(NSString*)deviceKey; +- (void)storeSession:(MXOlmSession*)session; /** Retrieve an end-to-end session between this device and another device. @@ -291,6 +295,12 @@ */ - (NSArray*)sessionsWithDevice:(NSString*)deviceKey; +/** + Retrieve all end-to-end sessions between this device and all other devices + + @return a array of end-to-end sessions. + */ +- (NSArray*)sessions; /** Store inbound group sessions. diff --git a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m index b7d75ce1c4..0a73e6b6d1 100644 --- a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m +++ b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m @@ -478,6 +478,11 @@ - (void)open:(void (^)(void))onComplete failure:(void (^)(NSError *error))failur onComplete(); } +- (NSString *)userId +{ + return self.accountInCurrentThread.userId; +} + - (void)storeDeviceId:(NSString*)deviceId { MXRealmOlmAccount *account = self.accountInCurrentThread; @@ -821,7 +826,7 @@ - (MXRealmRoomAlgorithm *)realmRoomAlgorithmForRoom:(NSString*)roomId inRealm:(R } -- (void)storeSession:(MXOlmSession*)session forDevice:(NSString*)deviceKey +- (void)storeSession:(MXOlmSession*)session { __block BOOL isNew = NO; NSDate *startDate = [NSDate date]; @@ -829,7 +834,7 @@ - (void)storeSession:(MXOlmSession*)session forDevice:(NSString*)deviceKey RLMRealm *realm = self.realm; [realm transactionWithName:@"[MXRealmCryptoStore] storeSession" block:^{ - MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:realm where:@"sessionId = %@ AND deviceKey = %@", session.session.sessionIdentifier, deviceKey].firstObject; + MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:realm where:@"sessionId = %@ AND deviceKey = %@", session.session.sessionIdentifier, session.deviceKey].firstObject; if (realmOlmSession) { // Update the existing one @@ -841,7 +846,7 @@ - (void)storeSession:(MXOlmSession*)session forDevice:(NSString*)deviceKey isNew = YES; realmOlmSession = [[MXRealmOlmSession alloc] initWithValue:@{ @"sessionId": session.session.sessionIdentifier, - @"deviceKey": deviceKey, + @"deviceKey": session.deviceKey, @"olmSessionData": [NSKeyedArchiver archivedDataWithRootObject:session.session] }]; realmOlmSession.lastReceivedMessageTs = session.lastReceivedMessageTs; @@ -863,7 +868,7 @@ - (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*) { OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; } @@ -879,7 +884,7 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS { OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; block(mxOlmSession); @@ -914,7 +919,7 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS { OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; [sessionsWithDevice addObject:mxOlmSession]; @@ -924,6 +929,27 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS return sessionsWithDevice; } +- (NSArray*)sessions +{ + NSMutableArray *sessions = [NSMutableArray array]; + + RLMResults *realmOlmSessions = [MXRealmOlmSession allObjectsInRealm:self.realm]; + for (MXRealmOlmSession *realmOlmSession in realmOlmSessions) + { + if (realmOlmSession.olmSessionData) + { + OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; + + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; + mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; + + [sessions addObject:mxOlmSession]; + } + } + + return sessions; +} + #pragma mark - MXRealmOlmInboundGroupSession - (void)storeInboundGroupSessions:(NSArray*)sessions diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index 96f64ecbc9..68d6fb225f 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -76,6 +76,7 @@ private class MXCryptoV2: NSObject, MXCrypto { private let keyVerification: MXKeyVerificationManagerV2 private var startTask: Task<(), Never>? private var roomEventObserver: Any? + private let cryptoLog = MXCryptoMachineLogger() private let log = MXNamedLog(name: "MXCryptoV2") // MARK: - Public properties diff --git a/MatrixSDK/Crypto/MXOlmDevice.m b/MatrixSDK/Crypto/MXOlmDevice.m index ae9d06dd8b..9ebf69032d 100644 --- a/MatrixSDK/Crypto/MXOlmDevice.m +++ b/MatrixSDK/Crypto/MXOlmDevice.m @@ -172,14 +172,14 @@ - (NSString *)createOutboundSession:(NSString *)theirIdentityKey theirOneTimeKey if (olmSession) { - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:theirIdentityKey]; // Pretend we've received a message at this point, otherwise // if we try to send a message to the device, it won't use // this session [mxOlmSession didReceiveMessage]; - [store storeSession:mxOlmSession forDevice:theirIdentityKey]; + [store storeSession:mxOlmSession]; return olmSession.sessionIdentifier; } else if (error) @@ -221,13 +221,13 @@ - (NSString*)createInboundSession:(NSString*)theirDeviceIdentityKey messageType: MXLogDebug(@"[MXOlmDevice] createInboundSession. decryptMessage error: %@", error); } - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:theirDeviceIdentityKey]; // This counts as a received message: set last received message time // to now [mxOlmSession didReceiveMessage]; - [store storeSession:mxOlmSession forDevice:theirDeviceIdentityKey]; + [store storeSession:mxOlmSession]; } return olmSession.sessionIdentifier; diff --git a/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift b/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift new file mode 100644 index 0000000000..b0a2cadc06 --- /dev/null +++ b/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift @@ -0,0 +1,193 @@ +// +// Copyright 2023 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 OLMKit +import MatrixSDKCrypto + +struct MXCryptoMigrationStore { + enum Error: Swift.Error { + case missingAccount + } + + let legacyStore: MXCryptoStore + + func extractData(with pickleKey: Data) throws -> MigrationData { + return .init( + account: try pickledAccount(pickleKey: pickleKey), + sessions: olmSessions(pickleKey: pickleKey), + inboundGroupSessions: megolmSessions(pickleKey: pickleKey), + backupVersion: legacyStore.backupVersion, + backupRecoveryKey: backupRecoveryKey(), + pickleKey: [UInt8](pickleKey), + crossSigning: crossSigning(), + trackedUsers: trackedUsers() + ) + } + + private func pickledAccount(pickleKey: Data) throws -> PickledAccount { + guard + let userId = legacyStore.userId(), + let deviceId = legacyStore.deviceId(), + let account = legacyStore.account() + else { + throw Error.missingAccount + } + return try PickledAccount( + userId: userId, + deviceId: deviceId, + account: account, + pickleKey: pickleKey + ) + } + + private func olmSessions(pickleKey: Data) -> [PickledSession] { + return legacyStore + .sessions()? + .compactMap { + do { + return try PickledSession(session: $0, pickleKey: pickleKey) + } catch { + MXLog.error("[MXCryptoMigrationStore] cannot extract olm session", context: error) + return nil + } + } ?? [] + } + + private func megolmSessions(pickleKey: Data) -> [PickledInboundGroupSession] { + guard let sessions = legacyStore.inboundGroupSessions() else { + return [] + } + + let sessionsToBackup = Set( + legacyStore.inboundGroupSessions(toBackup: UInt.max) + .compactMap { $0.session?.sessionIdentifier() } + ) + + return sessions.compactMap { + do { + return try PickledInboundGroupSession( + session: $0, + pickleKey: pickleKey, + backedUp: !sessionsToBackup.contains($0.session?.sessionIdentifier() ?? "") + ) + } catch { + MXLog.error("[MXCryptoMigrationStore] cannot extract megolm session", context: error) + return nil + } + } + } + + private func backupRecoveryKey() -> String? { + guard let privateKey = secret(for: MXSecretId.keyBackup) else { + return nil + } + + let data = MXBase64Tools.data(fromBase64: privateKey) + return MXRecoveryKey.encode(data) + } + + private func crossSigning() -> CrossSigningKeyExport { + let master = secret(for: MXSecretId.crossSigningMaster) + let selfSigning = secret(for: MXSecretId.crossSigningSelfSigning) + let userSigning = secret(for: MXSecretId.crossSigningUserSigning) + + return .init( + masterKey: master, + selfSigningKey: selfSigning, + userSigningKey: userSigning + ) + } + + private func trackedUsers() -> [String] { + var users = [String]() + for (user, status) in legacyStore.deviceTrackingStatus() ?? [:] { + if status != 0 { + users.append(user) + } + } + return users + } + + private func secret(for secretId: Unmanaged) -> String? { + return legacyStore.secret(withSecretId: secretId.takeUnretainedValue() as String) + } +} + +private extension PickledAccount { + init( + userId: String, + deviceId: String, + account: OLMAccount, + pickleKey: Data + ) throws { + let pickle = try account.serializeData(withKey: pickleKey) + self.init( + userId: userId, + deviceId: deviceId, + pickle: pickle, + shared: true, // Not yet implemented + uploadedSignedKeyCount: 50 // Not yet implemented + ) + } +} + +private extension PickledSession { + init(session: MXOlmSession, pickleKey: Data) throws { + let pickle = try session.session.serializeData(withKey: pickleKey) + let time = "\(Int(session.lastReceivedMessageTs))" + + self.init( + pickle: pickle, + senderKey: session.deviceKey, + createdUsingFallbackKey: false, // Not yet implemented + creationTime: time, // Not yet implemented + lastUseTime: time + ) + } +} + +private extension PickledInboundGroupSession { + enum Error: Swift.Error { + case invalidSession + } + + init(session: MXOlmInboundGroupSession, pickleKey: Data, backedUp: Bool) throws { + guard + let senderKey = session.senderKey, + let roomId = session.roomId + else { + throw Error.invalidSession + } + + let pickle = try session.session.serializeData(withKey: pickleKey) + + self.init( + pickle: pickle, + senderKey: senderKey, + signingKey: session.keysClaimed ?? [:], + roomId: roomId, + forwardingChains: session.forwardingCurve25519KeyChain ?? [], + imported: session.isUntrusted, + backedUp: backedUp + ) + } +} + +#endif diff --git a/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift b/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift new file mode 100644 index 0000000000..8944dc32d9 --- /dev/null +++ b/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift @@ -0,0 +1,91 @@ +// +// 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 OLMKit +import MatrixSDKCrypto + +class MXCryptoMigrationV2: NSObject { + private let store: MXCryptoMigrationStore + private let log = MXNamedLog(name: "MXCryptoMachineMigration") + + init(legacyStore: MXCryptoStore) { + store = .init(legacyStore: legacyStore) + super.init() + OLMKit.sharedInstance().pickleKeyDelegate = self + } + + func migrateCrypto() throws { + log.debug("Starting migration") + + let data = try store.extractData(with: pickleKey()) + let url = try MXCryptoMachine.storeURL(for: data.account.userId) + + if FileManager.default.fileExists(atPath: url.path) { + try FileManager.default.removeItem(at: url) + } + + let details = """ + Migration summary + - user id : \(data.account.userId) + - device id : \(data.account.deviceId) + - olm_sessions : \(data.sessions.count) + - megolm_sessions : \(data.inboundGroupSessions.count) + - backup_key : \(data.backupRecoveryKey != nil ? "true" : "false") + - cross_signing : \(data.crossSigning.masterKey != nil ? "true" : "false") + - tracked_users : \(data.trackedUsers.count) + """ + log.debug(details) + + try migrate( + data: data, + path: url.path, + passphrase: nil, + progressListener: self + ) + + log.debug("Migration complete") + } +} + +extension MXCryptoMigrationV2: OLMKitPickleKeyDelegate { + public func pickleKey() -> Data { + let key = MXKeyProvider.sharedInstance() + .keyDataForData( + ofType: MXCryptoOlmPickleKeyDataType, + isMandatory: true, + expectedKeyType: .rawData + ) + + guard let key = key as? MXRawDataKey else { + log.failure("Wrong key") + return Data() + } + + return key.key + } +} + +extension MXCryptoMigrationV2: ProgressListener { + func onProgress(progress: Int32, total: Int32) { + log.debug("Migration progress \(progress) out of \(total)") + } +} + +#endif diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXKeysQuerySchedulerUnitTests.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXKeysQuerySchedulerUnitTests.swift index aa36e887aa..17f91ef4d0 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXKeysQuerySchedulerUnitTests.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXKeysQuerySchedulerUnitTests.swift @@ -71,37 +71,41 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { users: Set, completion: @escaping (Response) -> Void, failure: ((Swift.Error) -> Void)? = nil - ) { - Task.detached { - do { - let result = try await self.scheduler.query(users: users) - completion(result) - } catch { - failure?(error) + ) async { + return await withCheckedContinuation { continuation in + Task.detached { + continuation.resume() + + do { + let result = try await self.scheduler.query(users: users) + completion(result) + } catch { + failure?(error) + } } } } // MARK: - Tests - func test_queryAlice() { + func test_queryAlice() async { let exp = expectation(description: "exp") - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"] ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) XCTAssertQueriesCount(1) } - func test_queryAliceAndBob() { + func test_queryAliceAndBob() async { let exp = expectation(description: "exp") - query(users: ["alice", "bob"]) { response in + await query(users: ["alice", "bob"]) { response in XCTAssertEqual(response, [ "alice": ["A"], "bob": ["B"], @@ -109,33 +113,33 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) XCTAssertQueriesCount(1) } - func test_queryBobAfterAlice() { + func test_queryBobAfterAlice() async { let exp = expectation(description: "exp") exp.expectedFulfillmentCount = 2 - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) exp.fulfill() } - query(users: ["bob"]) { response in + await query(users: ["bob"]) { response in XCTAssertEqual(response, [ "bob": ["B"], ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) XCTAssertQueriesCount(2) } - func test_executeMultipleAliceQueriesOnce() { + func test_executeMultipleAliceQueriesOnce() async { queryStartSpy = { self.stubbedResult = .success([ "alice": ["A1", "A2"] @@ -145,28 +149,28 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { let exp = expectation(description: "exp") exp.expectedFulfillmentCount = 3 - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) exp.fulfill() } - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) exp.fulfill() } - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) // Three queries are made but since they query the same user, // second and third query will simply await the results of @@ -174,7 +178,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { XCTAssertQueriesCount(1) } - func test_executeEachAliceQuerySeparately() { + func test_executeEachAliceQuerySeparately() async { queryStartSpy = { self.stubbedResult = .success([ "alice": ["A1", "A2"] @@ -182,39 +186,39 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { } var exp = expectation(description: "exp") - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) exp = expectation(description: "exp") - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A1", "A2"], ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) exp = expectation(description: "exp") - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A1", "A2"], ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) // Each of the three queries is made when no other query // is ongoing, meaning they will all execute XCTAssertQueriesCount(3) } - func test_executeMultipleBobQueriesOnce() { + func test_executeMultipleBobQueriesOnce() async { queryStartSpy = { self.stubbedResult = .success([ "bob": ["B1", "B2"] @@ -224,7 +228,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { let exp = expectation(description: "exp") exp.expectedFulfillmentCount = 2 - query(users: ["alice", "bob"]) { response in + await query(users: ["alice", "bob"]) { response in XCTAssertEqual(response, [ "alice": ["A"], "bob": ["B"], @@ -232,7 +236,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - query(users: ["bob"]) { response in + await query(users: ["bob"]) { response in XCTAssertEqual(response, [ "alice": ["A"], "bob": ["B"], @@ -240,7 +244,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) // The second query contains a different set of users, // but since there is no user additional to the first @@ -248,7 +252,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { XCTAssertQueriesCount(1) } - func test_executeSecondBobQuerySeparately() { + func test_executeSecondBobQuerySeparately() async { queryStartSpy = { self.stubbedResult = .success([ "bob": ["B1", "B2"], @@ -259,7 +263,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { let exp = expectation(description: "exp") exp.expectedFulfillmentCount = 2 - query(users: ["alice", "bob"]) { response in + await query(users: ["alice", "bob"]) { response in XCTAssertEqual(response, [ "alice": ["A"], "bob": ["B"], @@ -267,7 +271,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - query(users: ["bob", "carol"]) { response in + await query(users: ["bob", "carol"]) { response in XCTAssertEqual(response, [ "bob": ["B1", "B2"], "carol": ["C"], @@ -275,7 +279,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) // The second query contains one user shared with the first, // but also one new user, meaning the second query cannot be @@ -283,11 +287,11 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { XCTAssertQueriesCount(2) } - func test_nextQueryAggregatesPendingUsers() { + func test_nextQueryAggregatesPendingUsers() async { let exp = expectation(description: "exp") exp.expectedFulfillmentCount = 4 - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) @@ -296,7 +300,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { // Making three future / pending queries has the same outcome // as making a single query with all users aggregated. - query(users: ["bob"]) { response in + await query(users: ["bob"]) { response in XCTAssertEqual(response, [ "bob": ["B"], "carol": ["C"], @@ -305,7 +309,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - query(users: ["carol"]) { response in + await query(users: ["carol"]) { response in XCTAssertEqual(response, [ "bob": ["B"], "carol": ["C"], @@ -314,7 +318,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - query(users: ["david"]) { response in + await query(users: ["david"]) { response in XCTAssertEqual(response, [ "bob": ["B"], "carol": ["C"], @@ -323,22 +327,22 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) XCTAssertQueriesCount(2) } - func test_pendingUsersResetAfterQuery() { + func test_pendingUsersResetAfterQuery() async { var exp = expectation(description: "exp") exp.expectedFulfillmentCount = 3 - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) exp.fulfill() } - query(users: ["bob"]) { response in + await query(users: ["bob"]) { response in XCTAssertEqual(response, [ "bob": ["B"], "carol": ["C"], @@ -346,7 +350,7 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - query(users: ["carol"]) { response in + await query(users: ["carol"]) { response in XCTAssertEqual(response, [ "bob": ["B"], "carol": ["C"], @@ -354,12 +358,12 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) exp = expectation(description: "exp") exp.expectedFulfillmentCount = 2 - query(users: ["alice"]) { response in + await query(users: ["alice"]) { response in XCTAssertEqual(response, [ "alice": ["A"], ]) @@ -369,19 +373,19 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { // Even though we have previously aggregated some users, // once that query completed, all future queries will // start with a clean list again - query(users: ["david"]) { response in + await query(users: ["david"]) { response in XCTAssertEqual(response, [ "david": ["D"], ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) XCTAssertQueriesCount(4) } - func test_queryFail() { + func test_queryFail() async { scheduler = MXKeysQueryScheduler { _ in try! await Task.sleep(nanoseconds: 1_000_000) throw Error.dummy @@ -389,17 +393,17 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { let exp = expectation(description: "exp") - query(users: ["alice"], completion: { _ in + await query(users: ["alice"], completion: { _ in XCTFail("Should not succeed") }, failure: { error in XCTAssertEqual(error as? Error, Error.dummy) exp.fulfill() }) - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) } - func test_queryBobAfterFail() { + func test_queryBobAfterFail() async { stubbedResult = .failure(Error.dummy) queryStartSpy = { self.stubbedResult = .success([ @@ -410,21 +414,21 @@ class MXKeysQuerySchedulerUnitTests: XCTestCase { let exp = expectation(description: "exp") exp.expectedFulfillmentCount = 2 - query(users: ["alice"], completion: { _ in + await query(users: ["alice"], completion: { _ in XCTFail("Should not succeed") }, failure: { error in XCTAssertEqual(error as? Error, Error.dummy) exp.fulfill() }) - query(users: ["bob"]) { response in + await query(users: ["bob"]) { response in XCTAssertEqual(response, [ "bob": ["B"] ]) exp.fulfill() } - waitForExpectations(timeout: 1) + await waitForExpectations(timeout: 1) } // MARK: - Helpers diff --git a/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift index 80dcefb291..7febe3a9e3 100644 --- a/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift +++ b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift @@ -75,6 +75,12 @@ public class MXMemoryCryptoStore: NSObject, MXCryptoStore { public func open(_ onComplete: (() -> Void)!, failure: ((Error?) -> Void)!) { onComplete?() } + + // MARK: - User ID + + public func userId() -> String! { + storeAccount?.userId + } // MARK: - Device ID @@ -184,8 +190,8 @@ public class MXMemoryCryptoStore: NSObject, MXCryptoStore { // MARK: - OLM Session - public func store(_ session: MXOlmSession!, forDevice deviceKey: String!) { - let key = OlmSessionMapKey(sessionId: session.session.sessionIdentifier(), deviceKey: deviceKey) + public func store(_ session: MXOlmSession!) { + let key = OlmSessionMapKey(sessionId: session.session.sessionIdentifier(), deviceKey: session.deviceKey) olmSessions[key] = session } @@ -202,6 +208,10 @@ public class MXMemoryCryptoStore: NSObject, MXCryptoStore { public func sessions(withDevice deviceKey: String!) -> [MXOlmSession]! { Array(olmSessions.filter { $0.key.deviceKey == deviceKey }.values) } + + public func sessions() -> [MXOlmSession]! { + Array(olmSessions.values) + } // MARK: - Inbound Group Sessions diff --git a/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift b/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift new file mode 100644 index 0000000000..6cae30420a --- /dev/null +++ b/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift @@ -0,0 +1,231 @@ +// +// Copyright 2023 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 +import XCTest +@testable import MatrixSDK + +#if DEBUG + +import MatrixSDKCrypto + +class MXCryptoMigrationStoreUnitTests: XCTestCase { + + var pickleKey: Data! + var legacyStore: MXMemoryCryptoStore! + var store: MXCryptoMigrationStore! + + override func setUp() { + pickleKey = "1234".data(using: .ascii)! + + let credentials = MXCredentials() + credentials.userId = "Alice" + credentials.deviceId = "ABC" + + legacyStore = MXMemoryCryptoStore(credentials: credentials) + legacyStore.setAccount(OLMAccount(newAccount: ())) + + store = .init(legacyStore: legacyStore) + } + + // MARK: - Helpers + + func extractData(pickleKey: Data? = nil) throws -> MigrationData { + try store.extractData(with: pickleKey ?? self.pickleKey) + } + + @discardableResult + func storeGroupSession( + roomId: String = "ABC", + senderKey: String? = "Bob", + isUntrusted: Bool = false, + backedUp: Bool = false + ) -> MXOlmInboundGroupSession { + let device = MXOlmDevice(store: legacyStore)! + let outbound = device.createOutboundGroupSessionForRoom(withRoomId: roomId) + + let session = MXOlmInboundGroupSession(sessionKey: outbound!.sessionKey)! + session.senderKey = senderKey + session.roomId = roomId + session.keysClaimed = ["A": "1"] + session.isUntrusted = isUntrusted + legacyStore.store([session]) + + if backedUp { + legacyStore.markBackupDone(for: [session]) + } + return session + } + + func storeSecret(_ secret: String, secretId: Unmanaged) { + legacyStore.storeSecret(secret, withSecretId: secretId.takeUnretainedValue() as String) + } + + // MARK: - Tests + + func test_missingAccountFailsExtraction() { + legacyStore.setAccount(nil) + do { + _ = try extractData() + XCTFail("Should not succeed") + } catch MXCryptoMigrationStore.Error.missingAccount { + XCTAssert(true) + } catch { + XCTFail("Unknown error") + } + } + + func test_extractsAccount() throws { + let legacyPickle = try legacyStore.account().serializeData(withKey: pickleKey) + + let account = try extractData().account + + XCTAssertEqual(account.userId, "Alice") + XCTAssertEqual(account.deviceId, "ABC") + XCTAssertEqual(account.pickle, legacyPickle) + XCTAssertTrue(account.shared) + XCTAssertEqual(account.uploadedSignedKeyCount, 50) + } + + func test_extractsSession() throws { + let session = MXOlmSession(olmSession: OLMSession(), deviceKey: "XYZ") + session.lastReceivedMessageTs = 123 + legacyStore.store(session) + let pickle = try session.session.serializeData(withKey: pickleKey) + + let sessions = try extractData().sessions + + XCTAssertEqual(sessions.count, 1) + XCTAssertEqual(sessions[0].pickle, pickle) + XCTAssertEqual(sessions[0].senderKey, "XYZ") + XCTAssertFalse(sessions[0].createdUsingFallbackKey) + XCTAssertEqual(sessions[0].creationTime, "123") + XCTAssertEqual(sessions[0].lastUseTime, "123") + } + + func test_extractsMultipleSession() throws { + for i in 0 ..< 3 { + legacyStore.store(MXOlmSession(olmSession: OLMSession(), deviceKey: "\(i)")) + } + + let sessions = try extractData().sessions + + XCTAssertEqual(sessions.count, 3) + } + + func test_extractsGroupSession() throws { + let session = storeGroupSession(roomId: "abcd") + let pickle = try session.session.serializeData(withKey: pickleKey) + + let sessions = try extractData().inboundGroupSessions + + XCTAssertEqual(sessions.count, 1) + XCTAssertEqual(sessions[0].pickle, pickle) + XCTAssertEqual(sessions[0].senderKey, "Bob") + XCTAssertEqual(sessions[0].signingKey, ["A": "1"]) + XCTAssertEqual(sessions[0].roomId, "abcd") + XCTAssertEqual(sessions[0].forwardingChains, []) + } + + func test_extractsOnlyValidGroupSessions() throws { + for i in 0 ..< 4 { + let isValid = i % 2 == 0 + storeGroupSession(senderKey: isValid ? "Bob" : nil) + } + + let sessions = try extractData().inboundGroupSessions + + XCTAssertEqual(sessions.count, 2) + } + + func test_extractsImportedGroupSessionStatus() throws { + storeGroupSession(isUntrusted: true) + storeGroupSession(isUntrusted: false) + storeGroupSession(isUntrusted: false) + + let sessions = try extractData().inboundGroupSessions + + XCTAssertEqual(sessions.count, 3) + XCTAssertTrue(sessions[0].imported) + XCTAssertFalse(sessions[1].imported) + XCTAssertFalse(sessions[1].imported) + } + + func test_extractsBackedUpGroupSessionStatus() throws { + storeGroupSession(backedUp: false) + storeGroupSession(backedUp: true) + storeGroupSession(backedUp: false) + + let sessions = try extractData().inboundGroupSessions + + XCTAssertEqual(sessions.count, 3) + XCTAssertFalse(sessions[0].backedUp) + XCTAssertTrue(sessions[1].backedUp) + XCTAssertFalse(sessions[2].backedUp) + } + + func test_extractsBackupVersion() throws { + legacyStore.backupVersion = "5" + let version = try extractData().backupVersion + XCTAssertEqual(version, "5") + } + + func test_extractsBackupRecoveryKey() throws { + let privateKey = "ABCD" + storeSecret(privateKey, secretId: MXSecretId.keyBackup) + + let key = try extractData().backupRecoveryKey + + let recovery = MXRecoveryKey.encode(MXBase64Tools.data(fromBase64: privateKey)) + XCTAssertNotNil(key) + XCTAssertNotNil(recovery) + XCTAssertEqual(key, recovery) + } + + func test_extractsPickeKey() throws { + let pickleKey = "some key".data(using: .ascii)! + let key = try extractData(pickleKey: pickleKey).pickleKey + XCTAssertEqual(key, [UInt8](pickleKey)) + } + + func test_extractsCrossSigning() throws { + storeSecret("MASTER", secretId: MXSecretId.crossSigningMaster) + storeSecret("USER", secretId: MXSecretId.crossSigningUserSigning) + storeSecret("SELF", secretId: MXSecretId.crossSigningSelfSigning) + + let crossSigning = try extractData().crossSigning + + XCTAssertEqual(crossSigning.masterKey, "MASTER") + XCTAssertEqual(crossSigning.userSigningKey, "USER") + XCTAssertEqual(crossSigning.selfSigningKey, "SELF") + } + + func test_extractsOnlyTrackedUsers() throws { + let users = [ + "Alice": MXDeviceTrackingStatusNotTracked, + "Bob": MXDeviceTrackingStatusPendingDownload, + "Carol": MXDeviceTrackingStatusDownloadInProgress, + "Dave": MXDeviceTrackingStatusUpToDate, + ].mapValues { NSNumber(value: $0.rawValue) } + legacyStore.storeDeviceTrackingStatus(users) + + let trackedUsers = try extractData().trackedUsers + + XCTAssertEqual(Set(trackedUsers), ["Bob", "Carol", "Dave"]) + } +} + +#endif diff --git a/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift b/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift new file mode 100644 index 0000000000..df449a6b35 --- /dev/null +++ b/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift @@ -0,0 +1,119 @@ +// +// Copyright 2023 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 MXCryptoMigrationV2Tests: XCTestCase { + class KeyProvider: NSObject, MXKeyProviderDelegate { + func isEncryptionAvailableForData(ofType dataType: String) -> Bool { + return true + } + + func hasKeyForData(ofType dataType: String) -> Bool { + return true + } + + func keyDataForData(ofType dataType: String) -> MXKeyData? { + MXRawDataKey(key: "1234".data(using: .ascii)!) + } + } + + var data: MatrixSDKTestsData! + var e2eData: MatrixSDKTestsE2EData! + var keyProvider: KeyProvider! + + override func setUp() { + data = MatrixSDKTestsData() + e2eData = MatrixSDKTestsE2EData(matrixSDKTestsData: data) + + keyProvider = KeyProvider() + MXKeyProvider.sharedInstance().delegate = keyProvider + } + + override func tearDown() { + MXKeyProvider.sharedInstance().delegate = nil + } + + func test_canDecryptMessageAfterMigratingLegacyCrypto() throws { + e2eData.doE2ETestWithAliceAndBob(inARoom: self, cryptedBob: true, warnOnUnknowDevices: false) { aliceSession, bobSession, roomId, exp in + guard + let session = aliceSession, + let userId = session.myUserId, + let deviceId = session.myDeviceId, + let store = session.legacyCrypto?.store, + let room = session.room(withRoomId: roomId) + else { + XCTFail("Missing dependencies") + return + } + + var event: MXEvent! + let clearTextMessage = "Hi bob" + + // Send clear text message to an E2E room + room.sendTextMessage(clearTextMessage, localEcho: &event) { _ in + + // Erase cleartext and make sure the event was indeed encrypted + event.setClearData(nil) + XCTAssertTrue(event.isEncrypted) + XCTAssertEqual(event.content["algorithm"] as? String, kMXCryptoMegolmAlgorithm) + XCTAssertNotNil(event.content["ciphertext"]) + + // Migrate data using crypto v2 migration and legacy store + do { + let migration = MXCryptoMigrationV2(legacyStore: store) + try migration.migrateCrypto() + } catch { + XCTFail("Cannot migrate - \(error)") + } + + // Now instantiate crypto machine (crypto v2) that should be able to find + // the migrated data and use it to decrypt the event + do { + let url = try MXCryptoMachine.storeURL(for: userId) + let machine = try OlmMachine( + userId: userId, + deviceId: deviceId, + path: url.path, + passphrase: nil + ) + + let decrypted = try machine.decryptRoomEvent(event: event.jsonString() ?? "", roomId: roomId!) + let result = try MXEventDecryptionResult(event: decrypted) + let content = result.clearEvent["content"] as? [String: Any] + + // At this point we should be able to read back the original message after + // having decrypted the event with room keys migrated earlier + XCTAssertEqual(content?["body"] as? String, clearTextMessage) + + } catch { + XCTFail("Cannot decrypt - \(error)") + } + + session.close() + bobSession?.close() + exp?.fulfill() + } + } + } +} + +#endif diff --git a/MatrixSDKTests/MXCryptoTests.m b/MatrixSDKTests/MXCryptoTests.m index 77ee6dba09..1cd1fb68ae 100644 --- a/MatrixSDKTests/MXCryptoTests.m +++ b/MatrixSDKTests/MXCryptoTests.m @@ -2174,7 +2174,7 @@ - (void)xtestOlmSessionUnwedging aliceSession2.legacyCrypto.warnOnUnknowDevices = NO; // Let us wedge the session now. Set crypto state like after the first message - [aliceSession2.legacyCrypto.store storeSession:olmSession forDevice:bobSession.crypto.deviceCurve25519Key]; + [aliceSession2.legacyCrypto.store storeSession:olmSession]; // - Alice sends a 3rd message with a 3rd megolm session but a wedged olm session MXRoom *roomFromAlicePOV2 = [aliceSession2 roomWithRoomId:roomId]; diff --git a/MatrixSDKTests/TestPlans/CryptoTests.xctestplan b/MatrixSDKTests/TestPlans/CryptoTests.xctestplan index 96ada03559..e0bb49f051 100644 --- a/MatrixSDKTests/TestPlans/CryptoTests.xctestplan +++ b/MatrixSDKTests/TestPlans/CryptoTests.xctestplan @@ -33,6 +33,7 @@ "MXCrossSigningTests", "MXCrossSigningVerificationTests", "MXCryptoKeyVerificationTests", + "MXCryptoMigrationV2Tests", "MXCryptoSecretStorageTests", "MXCryptoShareTests", "MXCryptoTests", diff --git a/MatrixSDKTests/TestPlans/UnitTests.xctestplan b/MatrixSDKTests/TestPlans/UnitTests.xctestplan index f46bcfaa7e..97d3e21d66 100644 --- a/MatrixSDKTests/TestPlans/UnitTests.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTests.xctestplan @@ -45,6 +45,7 @@ "MXCrossSigningV2UnitTests", "MXCryptoKeyBackupEngineUnitTests", "MXCryptoMachineUnitTests", + "MXCryptoMigrationStoreUnitTests", "MXCryptoRequestsUnitTests", "MXDeviceInfoSourceUnitTests", "MXDeviceInfoUnitTests", diff --git a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan index 95253480c8..091a1f90c8 100644 --- a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan @@ -55,6 +55,7 @@ "MXCrossSigningV2UnitTests", "MXCryptoKeyBackupEngineUnitTests", "MXCryptoMachineUnitTests", + "MXCryptoMigrationStoreUnitTests", "MXCryptoRequestsUnitTests", "MXDeviceInfoSourceUnitTests", "MXDeviceInfoUnitTests", diff --git a/changelog.d/pr-1681.change b/changelog.d/pr-1681.change new file mode 100644 index 0000000000..2a1ffe6023 --- /dev/null +++ b/changelog.d/pr-1681.change @@ -0,0 +1 @@ +CryptoV2: Create crypto migration data