diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 5fff74b920..1a228bd205 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1841,8 +1841,6 @@ ED28068828F06D360070AE9F /* MXQRCodeTransactionV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED28068628F06D360070AE9F /* MXQRCodeTransactionV2.swift */; }; ED2DD114286C450600F06731 /* MXCryptoMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD111286C450600F06731 /* MXCryptoMachine.swift */; }; ED2DD115286C450600F06731 /* MXCryptoMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD111286C450600F06731 /* MXCryptoMachine.swift */; }; - ED2DD116286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD112286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift */; }; - ED2DD117286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD112286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift */; }; ED2DD118286C450600F06731 /* MXCryptoRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD113286C450600F06731 /* MXCryptoRequests.swift */; }; ED2DD119286C450600F06731 /* MXCryptoRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD113286C450600F06731 /* MXCryptoRequests.swift */; }; ED2DD11D286C4F4400F06731 /* MXCryptoRequestsUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD11B286C4F3E00F06731 /* MXCryptoRequestsUnitTests.swift */; }; @@ -1858,6 +1856,8 @@ ED4114EC292E498100728459 /* MXBackgroundCryptoV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4114EA292E498100728459 /* MXBackgroundCryptoV2.swift */; }; ED4114EE292E49C000728459 /* MXLegacyBackgroundCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4114ED292E49C000728459 /* MXLegacyBackgroundCrypto.swift */; }; ED4114EF292E49C000728459 /* MXLegacyBackgroundCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4114ED292E49C000728459 /* MXLegacyBackgroundCrypto.swift */; }; + ED4368B129784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */; }; + ED4368B229784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */; }; ED44F01128180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */; }; ED44F01228180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */; }; ED44F01428180EAB00452A5D /* MXSharedHistoryKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */; }; @@ -1904,6 +1904,18 @@ ED5C754928B3E80300D24E85 /* MXLogObjcWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = ED5C753B28B3E80300D24E85 /* MXLogObjcWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; ED5C95CE2833E85600843D82 /* MXOlmDeviceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5C95CD2833E85600843D82 /* MXOlmDeviceUnitTests.swift */; }; ED5C95CF2833E85600843D82 /* MXOlmDeviceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5C95CD2833E85600843D82 /* MXOlmDeviceUnitTests.swift */; }; + ED5EF145297AB1F200A5ADDA /* MXRoomEventEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF144297AB1F200A5ADDA /* MXRoomEventEncryption.swift */; }; + ED5EF146297AB1F200A5ADDA /* MXRoomEventEncryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF144297AB1F200A5ADDA /* MXRoomEventEncryption.swift */; }; + ED5EF14B297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF148297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift */; }; + ED5EF14C297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF148297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift */; }; + ED5EF14D297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF149297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift */; }; + ED5EF14E297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF149297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift */; }; + ED5EF14F297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF14A297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift */; }; + ED5EF150297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF14A297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift */; }; + ED5EF152297AB33E00A5ADDA /* MXCryptoV2Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF151297AB33E00A5ADDA /* MXCryptoV2Factory.swift */; }; + ED5EF153297AB33E00A5ADDA /* MXCryptoV2Factory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF151297AB33E00A5ADDA /* MXCryptoV2Factory.swift */; }; + ED5EF155297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF154297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift */; }; + ED5EF156297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED5EF154297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift */; }; ED647E3E292CE64400A47519 /* MXSessionStartupProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED647E3D292CE64400A47519 /* MXSessionStartupProgress.swift */; }; ED647E3F292CE64400A47519 /* MXSessionStartupProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED647E3D292CE64400A47519 /* MXSessionStartupProgress.swift */; }; ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */; }; @@ -3061,7 +3073,6 @@ ED28068328F06C6C0070AE9F /* QrCodeStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeStub.swift; sourceTree = ""; }; ED28068628F06D360070AE9F /* MXQRCodeTransactionV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXQRCodeTransactionV2.swift; sourceTree = ""; }; ED2DD111286C450600F06731 /* MXCryptoMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoMachine.swift; sourceTree = ""; }; - ED2DD112286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MXEventDecryptionResult+DecryptedEvent.swift"; sourceTree = ""; }; ED2DD113286C450600F06731 /* MXCryptoRequests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoRequests.swift; sourceTree = ""; }; ED2DD11B286C4F3E00F06731 /* MXCryptoRequestsUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoRequestsUnitTests.swift; sourceTree = ""; }; ED35652B281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXOlmInboundGroupSessionUnitTests.swift; sourceTree = ""; }; @@ -3070,6 +3081,7 @@ ED4114E7292E496C00728459 /* MXBackgroundCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXBackgroundCrypto.swift; sourceTree = ""; }; ED4114EA292E498100728459 /* MXBackgroundCryptoV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXBackgroundCryptoV2.swift; sourceTree = ""; }; ED4114ED292E49C000728459 /* MXLegacyBackgroundCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXLegacyBackgroundCrypto.swift; sourceTree = ""; }; + ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRealmCryptoStoreTests.swift; sourceTree = ""; }; ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyRequest.swift; sourceTree = ""; }; ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyManager.swift; sourceTree = ""; }; ED44F01728180F1C00452A5D /* MXSharedHistoryKeyManagerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyManagerUnitTests.swift; sourceTree = ""; }; @@ -3095,6 +3107,12 @@ ED5C753A28B3E80300D24E85 /* MXLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = ""; }; ED5C753B28B3E80300D24E85 /* MXLogObjcWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXLogObjcWrapper.h; sourceTree = ""; }; ED5C95CD2833E85600843D82 /* MXOlmDeviceUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXOlmDeviceUnitTests.swift; sourceTree = ""; }; + ED5EF144297AB1F200A5ADDA /* MXRoomEventEncryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomEventEncryption.swift; sourceTree = ""; }; + ED5EF148297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MXDeviceVerification+LocalTrust.swift"; sourceTree = ""; }; + ED5EF149297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MXRoomHistoryVisibility+HistoryVisibility.swift"; sourceTree = ""; }; + ED5EF14A297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MXEventDecryptionResult+DecryptedEvent.swift"; sourceTree = ""; }; + ED5EF151297AB33E00A5ADDA /* MXCryptoV2Factory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoV2Factory.swift; sourceTree = ""; }; + ED5EF154297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomEventEncryptionUnitTests.swift; sourceTree = ""; }; ED647E3D292CE64400A47519 /* MXSessionStartupProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSessionStartupProgress.swift; sourceTree = ""; }; ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfoFactory.swift; sourceTree = ""; }; ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfo.swift; sourceTree = ""; }; @@ -3498,6 +3516,7 @@ 322A51B41D9AB15900C8536D /* MXCrypto.h */, 322A51B51D9AB15900C8536D /* MXCrypto.m */, ED47CB6C28523995004FD755 /* MXCryptoV2.swift */, + ED5EF151297AB33E00A5ADDA /* MXCryptoV2Factory.swift */, 325D1C251DFECE0D0070B8BF /* MXCrypto_Private.h */, 322A51C51D9BBD3C00C8536D /* MXOlmDevice.h */, 322A51C61D9BBD3C00C8536D /* MXOlmDevice.m */, @@ -5360,6 +5379,7 @@ isa = PBXGroup; children = ( EDCB65E12912AB0C00F55D4D /* MXRoomEventDecryption.swift */, + ED5EF144297AB1F200A5ADDA /* MXRoomEventEncryption.swift */, ); path = RoomEvent; sourceTree = ""; @@ -5368,6 +5388,7 @@ isa = PBXGroup; children = ( ED1FE9052912D2EB0046F722 /* MXRoomEventDecryptionUnitTests.swift */, + ED5EF154297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift */, ); path = RoomEvents; sourceTree = ""; @@ -5423,10 +5444,10 @@ ED2DD110286C450600F06731 /* CryptoMachine */ = { isa = PBXGroup; children = ( + ED5EF147297AB28600A5ADDA /* Extensions */, ED2DD111286C450600F06731 /* MXCryptoMachine.swift */, ED5580722970265A003443E3 /* MXCryptoMachineLogger.swift */, ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */, - ED2DD112286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift */, ED2DD113286C450600F06731 /* MXCryptoRequests.swift */, EDC8C4072968A993003792C5 /* MXKeysQueryScheduler.swift */, ); @@ -5467,6 +5488,14 @@ path = Crypto; sourceTree = ""; }; + ED4368AF29784CA3002B6272 /* MXRealmCryptoStore */ = { + isa = PBXGroup; + children = ( + ED4368B029784CCE002B6272 /* MXRealmCryptoStoreTests.swift */, + ); + path = MXRealmCryptoStore; + sourceTree = ""; + }; ED44F01628180F1300452A5D /* KeySharing */ = { isa = PBXGroup; children = ( @@ -5541,6 +5570,16 @@ path = Logs; sourceTree = ""; }; + ED5EF147297AB28600A5ADDA /* Extensions */ = { + isa = PBXGroup; + children = ( + ED5EF148297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift */, + ED5EF14A297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift */, + ED5EF149297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift */, + ); + path = Extensions; + sourceTree = ""; + }; ED6DAC0428C771D500ECDCB6 /* RoomKeys */ = { isa = PBXGroup; children = ( @@ -5571,6 +5610,7 @@ ED6DAC1328C78D3700ECDCB6 /* Store */ = { isa = PBXGroup; children = ( + ED4368AF29784CA3002B6272 /* MXRealmCryptoStore */, ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */, ); path = Store; @@ -7090,6 +7130,7 @@ EC2EACFF266625170038B61F /* MXRoomLastMessage.m in Sources */, EDF1B6902876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */, EC8A539525B1BC77004E0802 /* MXUserModel.m in Sources */, + ED5EF14F297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */, 3252DCAF224BE5D40032264F /* MXKeyVerificationManager.m in Sources */, 323E0C5C1A306D7A00A31D73 /* MXEvent.m in Sources */, F03EF5011DF014D9009DF592 /* MXMediaManager.m in Sources */, @@ -7183,7 +7224,6 @@ B19A30A82404257700FB6F35 /* MXSASKeyVerificationStart.m in Sources */, ECCA02BB273485B200B6F34F /* MXThreadingService.swift in Sources */, 02CAD439217DD12F0074700B /* MXContentScanResult.m in Sources */, - ED2DD116286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */, EC60ED9C265CFE1700B39A4E /* MXRoomSyncState.m in Sources */, ED5C754028B3E80300D24E85 /* MXLog.swift in Sources */, 32133016228AF4EF0070BA9B /* MXRealmAggregationsStore.m in Sources */, @@ -7217,6 +7257,7 @@ 324AAC73239913AD00380A66 /* MXKeyVerificationDone.m in Sources */, ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */, B11556EE230C45C600B2A2CF /* MXIdentityServerRestClient.swift in Sources */, + ED5EF145297AB1F200A5ADDA /* MXRoomEventEncryption.swift in Sources */, EDAAC41F28E30F4C00DD89B5 /* (null) in Sources */, 321CFDE722525A49004D31DF /* MXSASTransaction.m in Sources */, EDDBA7F0293F353900AD1480 /* MXToDevicePayload.swift in Sources */, @@ -7264,12 +7305,14 @@ 32637ED51E5B00400011E20D /* MXDeviceList.m in Sources */, 327A5F55239805F600ED6329 /* MXKeyVerificationMac.m in Sources */, B17982FA2119E4A2001FD722 /* MXRoomCreateContent.m in Sources */, + ED5EF14B297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift in Sources */, 32A9E8261EF4026E0081358A /* MXUIKitBackgroundModeHandler.m in Sources */, B19A30D024042F0800FB6F35 /* MXSelfVerifyingMasterKeyTrustedQRCodeData.m in Sources */, 180F858627A2AF3000F4E5A5 /* MXWellKnownTileServerConfig.m in Sources */, B18B0E6725FBDC3000E32151 /* MXSpace.swift in Sources */, B146D4F721A5BB9F00D8C2C6 /* MXRealmMediaScanStore.m in Sources */, 32999DE422DCD1AD004FF987 /* MXPusherData.m in Sources */, + ED5EF14D297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift in Sources */, 322A51C81D9BBD3C00C8536D /* MXOlmDevice.m in Sources */, EDD578E72881C37C006739DD /* MXCryptoDeviceWrapper.swift in Sources */, EC60ED5F265CFC2C00B39A4E /* MXSyncResponse.m in Sources */, @@ -7295,6 +7338,7 @@ A780624E27B2CE74005780C0 /* FileManager+Backup.swift in Sources */, 323547D42226D3F500F15F94 /* MXWellKnown.m in Sources */, 320DFDE519DD99B60068622A /* MXRestClient.m in Sources */, + ED5EF152297AB33E00A5ADDA /* MXCryptoV2Factory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -7347,7 +7391,9 @@ EC51019D26C41981007D6D88 /* MXSyncResponseUnitTests.swift in Sources */, EDB4209527DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */, EC40385D28A16EDA0067D5B8 /* MXAes256KeyBackupTests.m in Sources */, + ED5EF155297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift in Sources */, ED6DAC0728C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */, + ED4368B129784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */, 3A9E2B4328EB3960000DB2A7 /* MXMatrixVersionsUnitTests.swift in Sources */, 3265CB3B1A151C3800E24B2F /* MXRoomStateTests.m in Sources */, ED8F1D302885AB0300F897E7 /* MXTrustLevelSourceUnitTests.swift in Sources */, @@ -7748,6 +7794,7 @@ EC8A53D925B1BCC6004E0802 /* MXThirdPartyProtocolInstance.m in Sources */, EDF1B6912876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */, B14EF2422397E90400758AF0 /* MXDeviceInfo.m in Sources */, + ED5EF150297AB29F00A5ADDA /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */, B14EF2432397E90400758AF0 /* MXIncomingSASTransaction.m in Sources */, B14EF2442397E90400758AF0 /* NSObject+sortedKeys.m in Sources */, B14EF2452397E90400758AF0 /* MXAggregatedReactionsUpdater.m in Sources */, @@ -7842,7 +7889,6 @@ B14EF2682397E90400758AF0 /* MXFilterJSONModel.m in Sources */, 325AD44223BE3E7500FF5277 /* MXCrossSigningInfo.m in Sources */, ECCA02BC273485B200B6F34F /* MXThreadingService.swift in Sources */, - ED2DD117286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift in Sources */, B14EF2692397E90400758AF0 /* MXMatrixVersions.m in Sources */, ED5C754128B3E80300D24E85 /* MXLog.swift in Sources */, B14EF26A2397E90400758AF0 /* MXReactionCountChangeListener.m in Sources */, @@ -7875,6 +7921,7 @@ B14EF2772397E90400758AF0 /* MXDecryptionResult.m in Sources */, ED6DABFD28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */, B14EF2782397E90400758AF0 /* MXTransactionCancelCode.m in Sources */, + ED5EF146297AB1F200A5ADDA /* MXRoomEventEncryption.swift in Sources */, EDAAC42028E30F4C00DD89B5 /* (null) in Sources */, B14EF2792397E90400758AF0 /* MXEventListener.m in Sources */, EDDBA7F1293F353900AD1480 /* MXToDevicePayload.swift in Sources */, @@ -7922,12 +7969,14 @@ B14EF2872397E90400758AF0 /* MXPusherData.m in Sources */, 324DD2A3246AE1EF00377005 /* MXEncryptedSecretContent.m in Sources */, EC8A53B625B1BC77004E0802 /* MXCallHangupEventContent.m in Sources */, + ED5EF14C297AB29F00A5ADDA /* MXDeviceVerification+LocalTrust.swift in Sources */, B14EF2882397E90400758AF0 /* MXOlmDevice.m in Sources */, B14766BA23D9D9420091F721 /* MXUsersTrustLevelSummary.m in Sources */, B14EF2892397E90400758AF0 /* MXKeyBackupPassword.m in Sources */, 180F858727A2AF3000F4E5A5 /* MXWellKnownTileServerConfig.m in Sources */, 3A108A9B25810F62005EEBE9 /* MXAesKeyData.m in Sources */, B14EF28A2397E90400758AF0 /* MXTools.m in Sources */, + ED5EF14E297AB29F00A5ADDA /* MXRoomHistoryVisibility+HistoryVisibility.swift in Sources */, B14EF28B2397E90400758AF0 /* MXRoomSummaryUpdater.m in Sources */, EDD578E82881C37C006739DD /* MXCryptoDeviceWrapper.swift in Sources */, B14EF28C2397E90400758AF0 /* MXScanRealmInMemoryProvider.m in Sources */, @@ -7953,6 +8002,7 @@ A780624F27B2CE74005780C0 /* FileManager+Backup.swift in Sources */, B14EF2932397E90400758AF0 /* MXRestClient.m in Sources */, EC60EDD3265CFECC00B39A4E /* MXRoomSyncSummary.m in Sources */, + ED5EF153297AB33E00A5ADDA /* MXCryptoV2Factory.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -8005,7 +8055,9 @@ EC51019E26C41981007D6D88 /* MXSyncResponseUnitTests.swift in Sources */, EDB4209627DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */, EC40385E28A16EDA0067D5B8 /* MXAes256KeyBackupTests.m in Sources */, + ED5EF156297AB93800A5ADDA /* MXRoomEventEncryptionUnitTests.swift in Sources */, ED6DAC0828C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */, + ED4368B229784CCE002B6272 /* MXRealmCryptoStoreTests.swift in Sources */, 3A9E2B4428EB3960000DB2A7 /* MXMatrixVersionsUnitTests.swift in Sources */, 32B477AA2638186000EA5800 /* MXHTTPAdditionalHeadersUnitTests.m in Sources */, B135066A27EA100100BD3276 /* MXBeaconInfoUnitTests.swift in Sources */, diff --git a/MatrixSDK/Background/MXBackgroundCryptoStore.m b/MatrixSDK/Background/MXBackgroundCryptoStore.m index cdeb1324f3..f2cdccf962 100644 --- a/MatrixSDK/Background/MXBackgroundCryptoStore.m +++ b/MatrixSDK/Background/MXBackgroundCryptoStore.m @@ -209,18 +209,6 @@ - (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*) return sessions; } -- (NSArray *)sessions -{ - 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]; @@ -356,6 +344,22 @@ - (void)storeDeviceSyncToken:(NSString*)deviceSyncToken NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); } +- (void)enumerateSessionsBy:(NSInteger)batchSize block:(void (^)(NSArray *, double))block +{ + NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); +} + +- (void)enumerateInboundGroupSessionsBy:(NSInteger)batchSize block:(void (^)(NSArray *, NSSet *, double))block +{ + NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); +} + +- (NSUInteger)sessionsCount +{ + NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); + return 0; +} + - (NSArray *)inboundGroupSessions { NSAssert(NO, @"This method should be useless in the context of MXBackgroundCryptoStore"); diff --git a/MatrixSDK/Contrib/Swift/Data/MXRoom.swift b/MatrixSDK/Contrib/Swift/Data/MXRoom.swift index de7a0c361d..752f1729e6 100644 --- a/MatrixSDK/Contrib/Swift/Data/MXRoom.swift +++ b/MatrixSDK/Contrib/Swift/Data/MXRoom.swift @@ -20,6 +20,25 @@ import Foundation public extension MXRoom { + enum Error: Swift.Error { + case missingState + } + + /** + The current state of the room. + */ + func state() async throws -> MXRoomState { + return try await withCheckedThrowingContinuation { cont in + state { + if let state = $0 { + cont.resume(returning: state) + } else { + cont.resume(throwing: Error.missingState) + } + } + } + } + /** The current list of members of the room. diff --git a/MatrixSDK/Crypto/Algorithms/RoomEvent/MXRoomEventEncryption.swift b/MatrixSDK/Crypto/Algorithms/RoomEvent/MXRoomEventEncryption.swift new file mode 100644 index 0000000000..b9c4dfb444 --- /dev/null +++ b/MatrixSDK/Crypto/Algorithms/RoomEvent/MXRoomEventEncryption.swift @@ -0,0 +1,220 @@ +// +// 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 + +/// Object responsible for encrypting room events and ensuring that room keys are distributed to room members +protocol MXRoomEventEncrypting { + + /// Check if a particular room is encrypted + func isRoomEncrypted(roomId: String) -> Bool + + /// Ensure that room keys have been shared with all eligible members + func ensureRoomKeysShared(roomId: String) async throws + + /// Encrypt event content and return encrypted data + func encrypt( + content: [AnyHashable: Any], + eventType: String, + in room: MXRoom + ) async throws -> [AnyHashable: Any] + + /// Respond to `m.room.encryption` event that may be setting a room encryption algorithm + func handleRoomEncryptionEvent(_ event: MXEvent) async throws +} + +struct MXRoomEventEncryption: MXRoomEventEncrypting { + enum Error: Swift.Error { + case missingRoom + case invalidEncryptionAlgorithm + } + + private static let keyRotationPeriodMsgs: Int = 100 // Rotate room keys after each 100 messages + private static let keyRotationPeriodSec: Int = 7 * 24 * 3600 // Rotate room keys each week + + private let handler: MXCryptoRoomEventEncrypting + private let legacyStore: MXCryptoStore + private let getRoomAction: GetRoomAction + private let log = MXNamedLog(name: "MXRoomEventEncryption") + + init( + handler: MXCryptoRoomEventEncrypting, + legacyStore: MXCryptoStore, + getRoomAction: @escaping GetRoomAction + ) { + self.handler = handler + self.legacyStore = legacyStore + self.getRoomAction = getRoomAction + } + + func isRoomEncrypted(roomId: String) -> Bool { + // State of room encryption is not yet implemented in `MatrixSDKCrypto` + // Will be moved to `MatrixSDKCrypto` eventually + return legacyStore.algorithm(forRoom: roomId) != nil + } + + func ensureRoomKeysShared(roomId: String) async throws { + let room = try room(for: roomId) + guard room.summary?.isEncrypted == true else { + log.debug("Room is not encrypted") + return + } + + try await ensureEncryptionAndRoomKeys(in: room) + } + + func encrypt( + content: [AnyHashable: Any], + eventType: String, + in room: MXRoom + ) async throws -> [AnyHashable: Any] { + + try await ensureEncryptionAndRoomKeys(in: room) + + let roomId = try roomId(for: room) + return try handler.encryptRoomEvent( + content: content, + roomId: roomId, + eventType: eventType + ) + } + + func handleRoomEncryptionEvent(_ event: MXEvent) async throws { + guard let roomId = event.roomId else { + return + } + + let room = try room(for: roomId) + let state = try await room.state() + try ensureRoomEncryption(roomId: roomId, algorithm: state.encryptionAlgorithm) + + let users = try await encryptionEligibleUsers( + for: room, + historyVisibility: state.historyVisibility + ) + handler.addTrackedUsers(users) + } + + // MARK: - Private + + /// Make sure we have adequately set the encryption algorithm for this room + /// and shared our current room key with all its members + private func ensureEncryptionAndRoomKeys(in room: MXRoom) async throws { + guard let roomId = room.roomId else { + throw Error.missingRoom + } + + let state = try await room.state() + try ensureRoomEncryption(roomId: roomId, algorithm: state.encryptionAlgorithm) + + let users = try await encryptionEligibleUsers( + for: room, + historyVisibility: state.historyVisibility + ) + + let settings = try encryptionSettings(for: state) + try await handler.shareRoomKeysIfNecessary( + roomId: roomId, + users: users, + settings: settings + ) + } + + /// Make sure that we recognize (and store if necessary) the claimed room encryption algorithm + private func ensureRoomEncryption(roomId: String, algorithm: String?) throws { + let existingAlgorithm = legacyStore.algorithm(forRoom: roomId) + if existingAlgorithm != nil && existingAlgorithm == algorithm { + log.debug("Encryption in room is already set to the correct algorithm") + return + } + + guard let algorithm = algorithm else { + log.error("Resetting encryption is not allowed") + throw Error.invalidEncryptionAlgorithm + } + + let supportedAlgorithms = Set([kMXCryptoMegolmAlgorithm]) + guard supportedAlgorithms.contains(algorithm) else { + log.error("Ignoring invalid room algorithm", context: [ + "room_id": roomId, + "algorithm": algorithm + ]) + throw Error.invalidEncryptionAlgorithm + } + + if let existing = existingAlgorithm, existing != algorithm { + log.warning("New m.room.encryption event in \(roomId) with an algorithm change from \(existing) to \(algorithm)") + } else { + log.debug("New m.room.encryption event with algorithm \(algorithm)") + } + + legacyStore.storeAlgorithm(forRoom: roomId, algorithm: algorithm) + } + + /// Get user ids for all room members that should be able to decrypt events, based on the history visibility setting + private func encryptionEligibleUsers( + for room: MXRoom, + historyVisibility: MXRoomHistoryVisibility? + ) async throws -> [String] { + guard + let members = try await room.members(), + let targetMembers = members.encryptionTargetMembers(historyVisibility?.identifier) + else { + log.error("Failed to get eligible users") + return [] + } + return targetMembers.compactMap(\.userId) + } + + private func encryptionSettings(for state: MXRoomState) throws -> EncryptionSettings { + guard let roomId = state.roomId else { + throw Error.missingRoom + } + + return .init( + algorithm: .megolmV1AesSha2, + rotationPeriod: UInt64(Self.keyRotationPeriodSec), + rotationPeriodMsgs: UInt64(Self.keyRotationPeriodMsgs), + // If not set, history visibility defaults to `joined` as the most restrictive setting + historyVisibility: state.historyVisibility?.visibility ?? .joined, + onlyAllowTrustedDevices: onlyTrustedDevices(in: roomId) + ) + } + + private func onlyTrustedDevices(in roomId: String) -> Bool { + return legacyStore.globalBlacklistUnverifiedDevices || legacyStore.blacklistUnverifiedDevices(inRoom: roomId) + } + + private func room(for roomId: String) throws -> MXRoom { + guard let room = getRoomAction(roomId) else { + throw Error.missingRoom + } + return room + } + + private func roomId(for room: MXRoom) throws -> String { + guard let roomId = room.roomId else { + throw Error.missingRoom + } + return roomId + } +} + +#endif diff --git a/MatrixSDK/Crypto/CryptoMachine/Extensions/MXDeviceVerification+LocalTrust.swift b/MatrixSDK/Crypto/CryptoMachine/Extensions/MXDeviceVerification+LocalTrust.swift new file mode 100644 index 0000000000..2b4e7bfae7 --- /dev/null +++ b/MatrixSDK/Crypto/CryptoMachine/Extensions/MXDeviceVerification+LocalTrust.swift @@ -0,0 +1,41 @@ +// +// 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 + +extension MXDeviceVerification { + var localTrust: LocalTrust { + switch self { + case .unverified: + return .unset + case .verified: + return .verified + case .blocked: + return .blackListed + case .unknown: + return .unset + @unknown default: + MXNamedLog(name: "MXDeviceVerification").failure("Unknown device verification", context: self) + return .unset + } + } +} + +#endif diff --git a/MatrixSDK/Crypto/CryptoMachine/MXEventDecryptionResult+DecryptedEvent.swift b/MatrixSDK/Crypto/CryptoMachine/Extensions/MXEventDecryptionResult+DecryptedEvent.swift similarity index 100% rename from MatrixSDK/Crypto/CryptoMachine/MXEventDecryptionResult+DecryptedEvent.swift rename to MatrixSDK/Crypto/CryptoMachine/Extensions/MXEventDecryptionResult+DecryptedEvent.swift diff --git a/MatrixSDK/Crypto/CryptoMachine/Extensions/MXRoomHistoryVisibility+HistoryVisibility.swift b/MatrixSDK/Crypto/CryptoMachine/Extensions/MXRoomHistoryVisibility+HistoryVisibility.swift new file mode 100644 index 0000000000..c90defd135 --- /dev/null +++ b/MatrixSDK/Crypto/CryptoMachine/Extensions/MXRoomHistoryVisibility+HistoryVisibility.swift @@ -0,0 +1,38 @@ +// +// 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 + +extension MXRoomHistoryVisibility { + var visibility: HistoryVisibility { + switch self { + case .worldReadable: + return .worldReadable + case .shared: + return .shared + case .invited: + return .invited + case .joined: + return .joined + } + } +} + +#endif diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index ba072848c6..d08c1375be 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -394,6 +394,10 @@ extension MXCryptoMachine: MXCryptoUserIdentitySource { } extension MXCryptoMachine: MXCryptoRoomEventEncrypting { + func addTrackedUsers(_ users: [String]) { + machine.updateTrackedUsers(users: users) + } + func shareRoomKeysIfNecessary( roomId: String, users: [String], diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift index e470fe456c..9a9f66b10a 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift @@ -64,6 +64,7 @@ protocol MXCryptoUserIdentitySource: MXCryptoIdentity { /// Room event encryption protocol MXCryptoRoomEventEncrypting: MXCryptoIdentity { + func addTrackedUsers(_ users: [String]) func shareRoomKeysIfNecessary(roomId: String, users: [String], settings: EncryptionSettings) async throws func encryptRoomEvent(content: [AnyHashable: Any], roomId: String, eventType: String) throws -> [String: Any] func discardRoomKey(roomId: String) diff --git a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h index 5741516cc1..ce789e5e50 100644 --- a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h +++ b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h @@ -296,11 +296,22 @@ - (NSArray*)sessionsWithDevice:(NSString*)deviceKey; /** - Retrieve all end-to-end sessions between this device and all other devices + Enumerate all end-to-end sessions in batches of `batchSize` + + Each block is internally wrapped in `@autoreleasepool` so that memory footprint remains constant + regardless of the number of stored sessions. + + @param batchSize the max number of sessions in a single batch + @param block function that will be executed with each batch, incl. list of sessions and current progress of batching + */ +- (void)enumerateSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + double progress))block; - @return a array of end-to-end sessions. +/** + The number of stored end-to-end sessions */ -- (NSArray*)sessions; +- (NSUInteger)sessionsCount; /** Store inbound group sessions. @@ -336,6 +347,19 @@ */ - (NSArray *)inboundGroupSessions; +/** + Enumerate all inbound group sessions in batches of `batchSize` + + Each block is internally wrapped in `@autoreleasepool` so that memory footprint remains constant + regardless of the number of stored sessions. + + @param batchSize the max number of sessions in a single batch + @param block function that will be executed with each batch, incl. list of sessions and current progress of batching + */ +- (void)enumerateInboundGroupSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + NSSet *backedUp, + double progress))block; /** Store outbound group session. diff --git a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m index 0a73e6b6d1..401f545d63 100644 --- a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m +++ b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m @@ -862,17 +862,7 @@ - (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*) { MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:self.realm where:@"sessionId = %@ AND deviceKey = %@", sessionId, deviceKey].firstObject; - - MXOlmSession *mxOlmSession; - if (realmOlmSession.olmSessionData) - { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - - mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; - } - - return mxOlmSession; + return [self olmSessionForRealmSession:realmOlmSession]; } - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSString*)sessionId block:(void (^)(MXOlmSession *olmSession))block @@ -880,16 +870,11 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS [self.realm transactionWithName:@"[MXRealmCryptoStore] performSessionOperationWithDevice" block:^{ MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:self.realm where:@"sessionId = %@ AND deviceKey = %@", sessionId, deviceKey].firstObject; - if (realmOlmSession.olmSessionData) + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; - - block(mxOlmSession); - - realmOlmSession.olmSessionData = [NSKeyedArchiver archivedDataWithRootObject:mxOlmSession.session]; + block(session); + realmOlmSession.olmSessionData = [NSKeyedArchiver archivedDataWithRootObject:session.session]; } else { @@ -915,14 +900,10 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS sessionsWithDevice = [NSMutableArray array]; } - if (realmOlmSession.olmSessionData) + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; - - [sessionsWithDevice addObject:mxOlmSession]; + [sessionsWithDevice addObject:session]; } } @@ -936,18 +917,64 @@ - (void)performSessionOperationWithDevice:(NSString*)deviceKey andSessionId:(NSS RLMResults *realmOlmSessions = [MXRealmOlmSession allObjectsInRealm:self.realm]; for (MXRealmOlmSession *realmOlmSession in realmOlmSessions) { - if (realmOlmSession.olmSessionData) + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) { - OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; + [sessions addObject:session]; + } + } + + return sessions; +} + +- (void)enumerateSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + double progress))block +{ + RLMResults *query = [MXRealmOlmSession allObjectsInRealm:self.realm]; + for (NSInteger i = 0; i < query.count; i += batchSize) + { + @autoreleasepool { + NSInteger count = MIN(batchSize, query.count - i); + NSIndexSet *batchSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, count)]; + MXLogDebug(@"[MXRealmCryptoStore] enumerateSessionsBy: Batch %@", batchSet); - MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmOlmSession.deviceKey]; - mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; + NSMutableArray *sessions = [NSMutableArray array]; + for (MXRealmOlmSession *realmOlmSession in [query objectsAtIndexes:batchSet]) + { + MXOlmSession *session = [self olmSessionForRealmSession:realmOlmSession]; + if (session) + { + [sessions addObject:session]; + } + } - [sessions addObject:mxOlmSession]; + double progress = (double)(batchSet.lastIndex + 1)/(double)query.count; + block(sessions.copy, progress); } } +} + +- (NSUInteger)sessionsCount +{ + RLMResults *sessions = [MXRealmOlmSession allObjectsInRealm:self.realm]; + return sessions.count; +} + +- (MXOlmSession *)olmSessionForRealmSession:(MXRealmOlmSession *)realmSession +{ + if (!realmSession.olmSessionData) + { + MXLogFailure(@"[MXRealmCryptoStore] olmSessionForRealmSession: Missing olm session data"); + return nil; + } - return sessions; + OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmSession.olmSessionData]; + + MXOlmSession *session = [[MXOlmSession alloc] initWithOlmSession:olmSession deviceKey:realmSession.deviceKey]; + session.lastReceivedMessageTs = realmSession.lastReceivedMessageTs; + + return session; } #pragma mark - MXRealmOlmInboundGroupSession @@ -1061,6 +1088,36 @@ - (void)performSessionOperationWithGroupSessionWithId:(NSString*)sessionId sende return sessions; } +- (void)enumerateInboundGroupSessionsBy:(NSInteger)batchSize + block:(void (^)(NSArray *sessions, + NSSet *backedUp, + double progress))block +{ + RLMResults *query = [MXRealmOlmInboundGroupSession allObjectsInRealm:self.realm]; + for (NSInteger i = 0; i < query.count; i += batchSize) + { + @autoreleasepool { + NSInteger count = MIN(batchSize, query.count - i); + NSIndexSet *batchSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(i, count)]; + MXLogDebug(@"[MXRealmCryptoStore] enumerateInboundGroupSessions: Batch %@", batchSet); + + NSMutableArray *sessions = [NSMutableArray array]; + NSMutableSet *backedUp = [NSMutableSet set]; + for (MXRealmOlmInboundGroupSession *realmSession in [query objectsAtIndexes:batchSet]) + { + [sessions addObject:[NSKeyedUnarchiver unarchiveObjectWithData:realmSession.olmInboundGroupSessionData]]; + if (realmSession.backedUp) + { + [backedUp addObject:realmSession.sessionId]; + } + } + + double progress = (double)(batchSet.lastIndex + 1)/(double)query.count; + block(sessions.copy, backedUp.copy, progress); + } + } +} + - (void)removeInboundGroupSessionWithId:(NSString*)sessionId andSenderKey:(NSString*)senderKey { RLMRealm *realm = self.realm; diff --git a/MatrixSDK/Crypto/MXCrypto.h b/MatrixSDK/Crypto/MXCrypto.h index 6334d6b712..d638850b0d 100644 --- a/MatrixSDK/Crypto/MXCrypto.h +++ b/MatrixSDK/Crypto/MXCrypto.h @@ -403,13 +403,17 @@ MX_ASSUME_MISSING_NULLABILITY_BEGIN error:(NSError **)error; /** - Check if the user has previously enabled crypto. - If yes, init the crypto module. + Initialize the crypto module + + If the user has previously enabled crypto it will be opened, otherwise a new crypto + store will be created. + @param migrationProgress a block called repeatedly with percentage of migration done, if any necessasry @param complete a block called in any case when the operation completes. */ -+ (void)checkCryptoWithMatrixSession:(MXSession*)mxSession - complete:(void (^)(id crypto, NSError *error))complete; ++ (void)initializeCryptoWithMatrixSession:(MXSession*)mxSession + migrationProgress:(void (^)(double progress))migrationProgress + complete:(void (^)(id crypto, NSError *error))complete; /** Stores the exportedOlmDevice related to the credentials into the store. diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index 2eff514c60..fdc3456de6 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -150,17 +150,33 @@ @implementation MXLegacyCrypto @synthesize keyVerificationManager = _keyVerificationManager; @synthesize recoveryService = _recoveryService; +#if DEBUG + ++ (MXCryptoV2Factory *)sharedCryptoV2Factory +{ + static MXCryptoV2Factory *factory = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + factory = [[MXCryptoV2Factory alloc] init]; + }); + + return factory; +} + +#endif + + (id)createCryptoWithMatrixSession:(MXSession *)mxSession error:(NSError **)error { __block id crypto; #ifdef MX_CRYPTO - #if DEBUG if (MXSDKOptions.sharedInstance.isCryptoSDKAvailable && MXSDKOptions.sharedInstance.enableCryptoSDK) { - return [self createCryptoV2WithSession:mxSession error:error]; + MXLogFailure(@"[MXCrypto] createCryptoWithMatrixSession: Crypto V2 should not be created directly, use initializeCryptoWithMatrixSession instead"); + return nil; } #endif @@ -177,27 +193,32 @@ @implementation MXLegacyCrypto return crypto; } -+ (void)checkCryptoWithMatrixSession:(MXSession *)mxSession - complete:(void (^)(id crypto, NSError *error))complete ++ (void)initializeCryptoWithMatrixSession:(MXSession *)mxSession + migrationProgress:(void (^)(double))migrationProgress + complete:(void (^)(id crypto, NSError *error))complete { #ifdef MX_CRYPTO #if DEBUG if (MXSDKOptions.sharedInstance.isCryptoSDKAvailable && MXSDKOptions.sharedInstance.enableCryptoSDK) { - NSError *error; - id crypto = [self createCryptoV2WithSession:mxSession error:&error]; - complete(crypto, error); + MXCryptoV2Factory *factory = [MXLegacyCrypto sharedCryptoV2Factory]; + [factory buildCryptoWithSession:mxSession migrationProgress:migrationProgress + success:^(id crypto) { + complete(crypto, nil); + } failure:^(NSError *error) { + complete(nil, error); + }]; return; } #endif - [self checkLegacyCryptoWithMatrixSession:mxSession complete:complete]; + [self initalizeLegacyCryptoWithMatrixSession:mxSession complete:complete]; #else complete(nil); #endif } -+ (void)checkLegacyCryptoWithMatrixSession:(MXSession*)mxSession complete:(void (^)(id crypto, NSError *error))complete ++ (void)initalizeLegacyCryptoWithMatrixSession:(MXSession*)mxSession complete:(void (^)(id crypto, NSError *error))complete { #ifdef MX_CRYPTO diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index be7adaec41..404b53ba1d 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -16,67 +16,34 @@ import Foundation -#if DEBUG -public extension MXLegacyCrypto { - enum CryptoError: Swift.Error, LocalizedError { - case cryptoNotAvailable - - public var errorDescription: String? { - return "Encryption not available, please restart the app" - } - } - - /// Create a Rust-based work-in-progress implementation of `MXCrypto` - @objc static func createCryptoV2(session: MXSession!) throws -> MXCrypto { - let log = MXNamedLog(name: "MXCryptoV2") - - guard let session = session else { - log.failure("Cannot create crypto V2, missing session") - throw CryptoError.cryptoNotAvailable - } - - do { - return try MXCryptoV2(session: session) - } catch { - log.failure("Error creating crypto V2", context: error) - throw CryptoError.cryptoNotAvailable - } - } -} -#endif - #if DEBUG import MatrixSDKCrypto /// An implementation of `MXCrypto` which uses [matrix-rust-sdk](https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto) /// under the hood. -private class MXCryptoV2: NSObject, MXCrypto { +class MXCryptoV2: NSObject, MXCrypto { enum Error: Swift.Error { - case missingCredentials - case missingRoom - case roomNotEncrypted case cannotUnsetTrust case backupNotEnabled } // MARK: - Private properties - private static let keyRotationPeriodMsgs: Int = 100 - private static let keyRotationPeriodSec: Int = 7 * 24 * 3600 - private weak var session: MXSession? - private let cryptoQueue: DispatchQueue private let legacyStore: MXCryptoStore + private let machine: MXCryptoMachine - private let roomEventDecryptor: MXRoomEventDecrypting + private let encryptor: MXRoomEventEncrypting + private let decryptor: MXRoomEventDecrypting private let deviceInfoSource: MXDeviceInfoSource private let trustLevelSource: MXTrustLevelSource private let backupEngine: MXCryptoKeyBackupEngine? + 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 @@ -101,37 +68,34 @@ private class MXCryptoV2: NSObject, MXCrypto { let crossSigning: MXCrossSigning let recoveryService: MXRecoveryService - init(session: MXSession) throws { - guard - let restClient = session.matrixRestClient, - let credentials = session.credentials, - let userId = credentials.userId, - let deviceId = credentials.deviceId - else { - throw Error.missingCredentials - } - + @MainActor + init( + userId: String, + deviceId: String, + session: MXSession, + restClient: MXRestClient, + legacyStore: MXCryptoStore + ) throws { self.session = session - self.cryptoQueue = DispatchQueue(label: "MXCryptoV2-\(userId)") + self.legacyStore = legacyStore - // 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 - if MXRealmCryptoStore.hasData(for: credentials) { - self.legacyStore = MXRealmCryptoStore(credentials: credentials) - } else { - self.legacyStore = MXRealmCryptoStore.createStore(with: credentials) + let getRoomAction: (String) -> MXRoom? = { [weak session] in + session?.room(withRoomId: $0) } machine = try MXCryptoMachine( userId: userId, deviceId: deviceId, restClient: restClient, - getRoomAction: { [weak session] roomId in - session?.room(withRoomId: roomId) - } + getRoomAction: getRoomAction ) - roomEventDecryptor = MXRoomEventDecryption(handler: machine) + encryptor = MXRoomEventEncryption( + handler: machine, + legacyStore: legacyStore, + getRoomAction: getRoomAction + ) + decryptor = MXRoomEventDecryption(handler: machine) deviceInfoSource = MXDeviceInfoSource(source: machine) trustLevelSource = MXTrustLevelSource( @@ -144,14 +108,18 @@ private class MXCryptoV2: NSObject, MXCrypto { handler: machine ) + // Some functionality not yet migrated to the rust-sdk (e.g. backup state machine, 4S ...) uses + // dispatch queues under the hood. We create one specific to crypto v2. + let legacyQueue = DispatchQueue(label: "org.matrix.sdk.MXCryptoV2") + if MXSDKOptions.sharedInstance().enableKeyBackupWhenStartingMXCrypto { - let engine = MXCryptoKeyBackupEngine(backup: machine, roomEventDecryptor: roomEventDecryptor) + let engine = MXCryptoKeyBackupEngine(backup: machine, roomEventDecryptor: decryptor) backupEngine = engine backup = MXKeyBackup( engine: engine, restClient: restClient, secretShareManager: MXSecretShareManager(), - queue: cryptoQueue + queue: legacyQueue ) } else { backupEngine = nil @@ -172,7 +140,7 @@ private class MXCryptoV2: NSObject, MXCrypto { backup: backup, secretStorage: MXSecretStorage( matrixSession: session, - processingQueue: cryptoQueue + processingQueue: legacyQueue ), secretStore: MXCryptoSecretStoreV2( backup: backup, @@ -180,7 +148,7 @@ private class MXCryptoV2: NSObject, MXCrypto { crossSigning: machine ), crossSigning: crossSigning, - cryptoQueue: cryptoQueue + cryptoQueue: legacyQueue ), delegate: crossSign ) @@ -194,6 +162,7 @@ private class MXCryptoV2: NSObject, MXCrypto { _ onComplete: (() -> Void)?, failure: ((Swift.Error) -> Void)? ) { + guard startTask == nil else { log.error("Crypto module has already been started") onComplete?() @@ -203,8 +172,6 @@ private class MXCryptoV2: NSObject, MXCrypto { log.debug("->") startTask = Task { do { - try migrateIfNecessary() - try await machine.uploadKeysIfNecessary() crossSigning.refreshState(success: nil) backup?.checkAndStart() @@ -231,7 +198,7 @@ private class MXCryptoV2: NSObject, MXCrypto { session?.removeListener(roomEventObserver) Task { - await roomEventDecryptor.resetUndecryptedEvents() + await decryptor.resetUndecryptedEvents() } if deleteStore { @@ -249,30 +216,10 @@ private class MXCryptoV2: NSObject, MXCrypto { } } - private func migrateIfNecessary() throws { - guard legacyStore.cryptoVersion.rawValue < MXCryptoVersion.versionLegacyDeprecated.rawValue else { - log.debug("Legacy crypto has already been deprecated, no need to migrate") - return - } - - log.debug("Requires migration from legacy crypto") - let migration = MXCryptoMigrationV2(legacyStore: legacyStore) - try migration.migrateCrypto() - - log.debug("Marking legacy crypto as deprecated") - legacyStore.cryptoVersion = MXCryptoVersion.versionLegacyDeprecated - } - // MARK: - Event Encryption public func isRoomEncrypted(_ roomId: String) -> Bool { - guard let summary = session?.room(withRoomId: roomId)?.summary else { - log.error("Missing room") - return false - } - // State of room encryption is not yet implemented in `MatrixSDKCrypto` - // Will be moved to `MatrixSDKCrypto` eventually - return summary.isEncrypted + return encryptor.isRoomEncrypted(roomId: roomId) } func encryptEventContent( @@ -288,31 +235,12 @@ private class MXCryptoV2: NSObject, MXCrypto { let stopTracking = MXSDKOptions.sharedInstance().analyticsDelegate? .startDurationTracking(forName: "MXCryptoV2", operation: "encryptEventContent") - guard let roomId = room.roomId else { - log.failure("Missing room id") - failure?(Error.missingRoom) - return nil - } - - guard isRoomEncrypted(roomId) else { - log.failure("Attempting to encrypt event in room without encryption") - failure?(Error.roomNotEncrypted) - return nil - } - Task { do { - let users = try await getRoomUserIds(for: room) - let settings = try encryptionSettings(for: room) - try await machine.shareRoomKeysIfNecessary( - roomId: roomId, - users: users, - settings: settings - ) - let result = try machine.encryptRoomEvent( + let result = try await encryptor.encrypt( content: eventContent, - roomId: roomId, - eventType: eventType + eventType: eventType, + in: room ) stopTracking?() @@ -346,7 +274,7 @@ private class MXCryptoV2: NSObject, MXCrypto { Task { log.debug("Decrypting \(events.count) event(s) in timeline \(timeline ?? "")") - let results = await roomEventDecryptor.decrypt(events: events) + let results = await decryptor.decrypt(events: events) await MainActor.run { onComplete?(results) } @@ -360,22 +288,9 @@ private class MXCryptoV2: NSObject, MXCrypto { ) -> MXHTTPOperation? { log.debug("->") - guard let room = session?.room(withRoomId: roomId) else { - log.failure("Missing room") - failure?(Error.missingRoom) - return nil - } - Task { do { - let users = try await getRoomUserIds(for: room) - let settings = try encryptionSettings(for: room) - try await machine.shareRoomKeysIfNecessary( - roomId: roomId, - users: users, - settings: settings - ) - + try await encryptor.ensureRoomKeysShared(roomId: roomId) log.debug("Room keys shared when necessary") await MainActor.run { success?() @@ -413,12 +328,14 @@ private class MXCryptoV2: NSObject, MXCrypto { // MARK: - Sync func handle(_ syncResponse: MXSyncResponse, onComplete: @escaping () -> Void) { - let toDeviceCount = syncResponse.toDevice?.events.count ?? 0 - let devicesChanged = syncResponse.deviceLists?.changed?.count ?? 0 - let devicesLeft = syncResponse.deviceLists?.left?.count ?? 0 - - MXLog.debug("[MXCryptoV2] --------------------------------") - log.debug("Handling new sync response with \(toDeviceCount) to-device event(s), \(devicesChanged) device(s) changed, \(devicesLeft) device(s) left") + let syncId = UUID().uuidString + let details = """ + Handling new sync response `\(syncId)` + - to-device events : \(syncResponse.toDevice?.events.count ?? 0) + - devices changed : \(syncResponse.deviceLists?.changed?.count ?? 0) + - devices left : \(syncResponse.deviceLists?.left?.count ?? 0) + """ + log.debug(details) Task { do { @@ -434,8 +351,7 @@ private class MXCryptoV2: NSObject, MXCrypto { log.error("Cannot handle sync", context: error) } - log.debug("Completing sync response") - MXLog.debug("[MXCryptoV2] --------------------------------") + log.debug("Completed handling sync response `\(syncId)`") await MainActor.run { onComplete() } @@ -450,7 +366,7 @@ private class MXCryptoV2: NSObject, MXCrypto { for event in toDeviceEvents { await keyVerification.handleDeviceEvent(event) restoreBackupIfPossible(event: event) - await roomEventDecryptor.handlePossibleRoomKeyEvent(event) + await decryptor.handlePossibleRoomKeyEvent(event) } if backupEngine?.enabled == true && backupEngine?.hasKeysToBackup() == true { @@ -745,11 +661,6 @@ private class MXCryptoV2: NSObject, MXCrypto { } } - private func getRoomUserIds(for room: MXRoom) async throws -> [String] { - return try await room.members()?.members - .compactMap(\.userId) ?? [] - } - private func crossSigningInfo(userIds: [String]) -> [String: MXCrossSigningInfo] { return userIds .compactMap(crossSigning.crossSigningKeys(forUser:)) @@ -757,61 +668,6 @@ private class MXCryptoV2: NSObject, MXCrypto { return dict[info.userId] = info } } - - private func encryptionSettings(for room: MXRoom) throws -> EncryptionSettings { - guard let roomId = room.roomId else { - throw Error.missingRoom - } - - let historyVisibility = try HistoryVisibility(identifier: room.summary.historyVisibility) - return .init( - algorithm: .megolmV1AesSha2, - rotationPeriod: UInt64(Self.keyRotationPeriodSec), - rotationPeriodMsgs: UInt64(Self.keyRotationPeriodMsgs), - historyVisibility: historyVisibility, - onlyAllowTrustedDevices: globalBlacklistUnverifiedDevices || isBlacklistUnverifiedDevices(inRoom: roomId) - ) - } -} - -private extension MXDeviceVerification { - var localTrust: LocalTrust { - switch self { - case .unverified: - return .unset - case .verified: - return .verified - case .blocked: - return .blackListed - case .unknown: - return .unset - @unknown default: - MXNamedLog(name: "MXDeviceVerification").failure("Unknown device verification", context: self) - return .unset - } - } -} - -private extension HistoryVisibility { - enum Error: Swift.Error { - case invalidVisibility - } - - init(identifier: String) throws { - guard let visibility = MXRoomHistoryVisibility(identifier: identifier) else { - throw Error.invalidVisibility - } - switch visibility { - case .worldReadable: - self = .worldReadable - case .shared: - self = .shared - case .invited: - self = .invited - case .joined: - self = .joined - } - } } #endif diff --git a/MatrixSDK/Crypto/MXCryptoV2Factory.swift b/MatrixSDK/Crypto/MXCryptoV2Factory.swift new file mode 100644 index 0000000000..70a973f08c --- /dev/null +++ b/MatrixSDK/Crypto/MXCryptoV2Factory.swift @@ -0,0 +1,131 @@ +// +// 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 + +@objc public class MXCryptoV2Factory: NSObject { + enum Error: Swift.Error { + case cryptoNotAvailable + case storeNotAvailable + } + + private let log = MXNamedLog(name: "MXCryptoV2Factory") + private let sdkLog = MXCryptoMachineLogger() + + @objc public func buildCrypto( + session: MXSession!, + migrationProgress: ((Double) -> Void)?, + success: @escaping (MXCrypto?) -> Void, + failure: @escaping (Swift.Error) -> Void + ) { + guard + let session = session, + let restClient = session.matrixRestClient, + let credentials = session.credentials, + let userId = credentials.userId, + let deviceId = credentials.deviceId + else { + log.failure("Missing required dependencies") + failure(Error.cryptoNotAvailable) + return + } + + log.debug("Building crypto module") + Task { + do { + let store = try await createOrOpenLegacyStore(credentials: credentials) + try self.migrateIfNecessary(legacyStore: store) { + migrationProgress?($0) + } + + let crypto = try await MXCryptoV2( + userId: userId, + deviceId: deviceId, + session: session, + restClient: restClient, + legacyStore: store + ) + await MainActor.run { + success(crypto) + } + } catch { + self.log.failure("Cannot create crypto") + await MainActor.run { + failure(error) + } + } + } + } + + // A few features (e.g. global untrusted users blacklist) are not yet implemented in `MatrixSDKCrypto` + // so they have to be stored in a legacy database. Will be moved to `MatrixSDKCrypto` eventually + private func createOrOpenLegacyStore(credentials: MXCredentials) async throws -> MXCryptoStore { + MXRealmCryptoStore.deleteReadonlyStore(with: credentials) + + if MXRealmCryptoStore.hasData(for: credentials) { + log.debug("Legacy crypto store exists") + + guard let legacyStore = MXRealmCryptoStore(credentials: credentials) else { + log.failure("Cannot initialize legacy store") + throw Error.storeNotAvailable + } + + try await openStore(legacyStore) + log.debug("Legacy crypto store opened") + return legacyStore + + } else { + log.debug("Creating new legacy crypto store") + + guard let legacyStore = MXRealmCryptoStore.createStore(with: credentials) else { + log.failure("Cannot create legacy store") + throw Error.storeNotAvailable + } + legacyStore.cryptoVersion = MXCryptoVersion.versionLegacyDeprecated + + log.debug("Legacy crypto store created") + return legacyStore + } + } + + private func openStore(_ store: MXCryptoStore) async throws { + try await withCheckedThrowingContinuation { cont in + store.open { + cont.resume(returning: ()) + } failure: { error in + cont.resume(throwing: error ?? Error.storeNotAvailable) + } + } + } + + private func migrateIfNecessary(legacyStore: MXCryptoStore, updateProgress: @escaping (Double) -> Void) throws { + guard legacyStore.cryptoVersion.rawValue < MXCryptoVersion.versionLegacyDeprecated.rawValue else { + log.debug("Legacy crypto has already been deprecatd, no need to migrate") + return + } + + log.debug("Requires migration from legacy crypto") + let migration = MXCryptoMigrationV2(legacyStore: legacyStore) + try migration.migrateCrypto(updateProgress: updateProgress) + + log.debug("Marking legacy crypto as deprecated") + legacyStore.cryptoVersion = MXCryptoVersion.versionLegacyDeprecated + } +} + +#endif diff --git a/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift b/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift index b0a2cadc06..25561d1366 100644 --- a/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift +++ b/MatrixSDK/Crypto/Migration/Data/MXCryptoMigrationStore.swift @@ -28,11 +28,19 @@ struct MXCryptoMigrationStore { let legacyStore: MXCryptoStore + var olmSessionCount: UInt { + legacyStore.sessionsCount() + } + + var megolmSessionCount: UInt { + legacyStore.inboundGroupSessionsCount(false) + } + func extractData(with pickleKey: Data) throws -> MigrationData { return .init( account: try pickledAccount(pickleKey: pickleKey), - sessions: olmSessions(pickleKey: pickleKey), - inboundGroupSessions: megolmSessions(pickleKey: pickleKey), + sessions: [], // Sessions are extracted in batches separately + inboundGroupSessions: [], // Group sessions are extracted in batches separately backupVersion: legacyStore.backupVersion, backupRecoveryKey: backupRecoveryKey(), pickleKey: [UInt8](pickleKey), @@ -41,6 +49,46 @@ struct MXCryptoMigrationStore { ) } + func extractSessions( + with pickleKey: Data, + batchSize: Int, + callback: @escaping ([PickledSession], Double) -> Void + ) { + legacyStore.enumerateSessions(by: batchSize) { sessions, progress in + let pickled: [PickledSession] = sessions?.compactMap { + do { + return try PickledSession(session: $0, pickleKey: pickleKey) + } catch { + MXLog.error("[MXCryptoMigrationStore] cannot extract olm session", context: error) + return nil + } + } ?? [] + callback(pickled, progress) + } + } + + func extractGroupSessions( + with pickleKey: Data, + batchSize: Int, + callback: @escaping ([PickledInboundGroupSession], Double) -> Void + ) { + legacyStore.enumerateInboundGroupSessions(by: batchSize) { sessions, backedUp, progress in + let pickled: [PickledInboundGroupSession] = sessions?.compactMap { + do { + return try PickledInboundGroupSession( + session: $0, + pickleKey: pickleKey, + backedUp: backedUp?.contains($0.session.sessionIdentifier()) == true + ) + } catch { + MXLog.error("[MXCryptoMigrationStore] cannot extract megolm session", context: error) + return nil + } + } ?? [] + callback(pickled, progress) + } + } + private func pickledAccount(pickleKey: Data) throws -> PickledAccount { guard let userId = legacyStore.userId(), @@ -57,43 +105,6 @@ struct MXCryptoMigrationStore { ) } - 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 diff --git a/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift b/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift index 8944dc32d9..a8b708cd47 100644 --- a/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift +++ b/MatrixSDK/Crypto/Migration/MXCryptoMigrationV2.swift @@ -22,6 +22,8 @@ import OLMKit import MatrixSDKCrypto class MXCryptoMigrationV2: NSObject { + private static let SessionBatchSize = 1000 + private let store: MXCryptoMigrationStore private let log = MXNamedLog(name: "MXCryptoMachineMigration") @@ -31,10 +33,12 @@ class MXCryptoMigrationV2: NSObject { OLMKit.sharedInstance().pickleKeyDelegate = self } - func migrateCrypto() throws { + func migrateCrypto(updateProgress: @escaping (Double) -> Void) throws { log.debug("Starting migration") + updateProgress(0) - let data = try store.extractData(with: pickleKey()) + let key = pickleKey() + let data = try store.extractData(with: key) let url = try MXCryptoMachine.storeURL(for: data.account.userId) if FileManager.default.fileExists(atPath: url.path) { @@ -45,8 +49,8 @@ class MXCryptoMigrationV2: NSObject { Migration summary - user id : \(data.account.userId) - device id : \(data.account.deviceId) - - olm_sessions : \(data.sessions.count) - - megolm_sessions : \(data.inboundGroupSessions.count) + - olm_sessions : \(store.olmSessionCount) + - megolm_sessions : \(store.megolmSessionCount) - backup_key : \(data.backupRecoveryKey != nil ? "true" : "false") - cross_signing : \(data.crossSigning.masterKey != nil ? "true" : "false") - tracked_users : \(data.trackedUsers.count) @@ -60,7 +64,69 @@ class MXCryptoMigrationV2: NSObject { progressListener: self ) + log.debug("Migrating olm sessions in batches") + + // How much does migration of olm vs megolm sessions contribute to the overall progress + let olmToMegolmProgressRatio = 0.25 + + store.extractSessions(with: key, batchSize: Self.SessionBatchSize) { [weak self] batch, progress in + updateProgress(progress * olmToMegolmProgressRatio) + + do { + try self?.migrateSessions( + data: data, + sessions: batch, + url: url + ) + } catch { + self?.log.error("Error migrating some sessions", context: error) + } + } + + log.debug("Migrating megolm sessions in batches") + + store.extractGroupSessions(with: key, batchSize: Self.SessionBatchSize) { [weak self] batch, progress in + updateProgress(olmToMegolmProgressRatio + progress * (1 - olmToMegolmProgressRatio)) + + do { + try self?.migrateSessions( + data: data, + inboundGroupSessions: batch, + url: url + ) + } catch { + self?.log.error("Error migrating some sessions", context: error) + } + } + log.debug("Migration complete") + updateProgress(1) + } + + // To migrate sessions in batches and keep memory under control we are repeatedly calling `migrate` + // function whilst only passing data for sessions and account, keeping the rest empty. + // This API will be improved in `MatrixCryptoSDK` in the future. + private func migrateSessions( + data: MigrationData, + sessions: [PickledSession] = [], + inboundGroupSessions: [PickledInboundGroupSession] = [], + url: URL + ) throws { + try migrate( + data: .init( + account: data.account, + sessions: sessions, + inboundGroupSessions: inboundGroupSessions, + backupVersion: data.backupVersion, + backupRecoveryKey: data.backupRecoveryKey, + pickleKey: data.pickleKey, + crossSigning: data.crossSigning, + trackedUsers: data.trackedUsers + ), + path: url.path, + passphrase: nil, + progressListener: self + ) } } @@ -84,7 +150,7 @@ extension MXCryptoMigrationV2: OLMKitPickleKeyDelegate { extension MXCryptoMigrationV2: ProgressListener { func onProgress(progress: Int32, total: Int32) { - log.debug("Migration progress \(progress) out of \(total)") + // Progress loggged manually } } diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 82b60b8eff..6366a24cdd 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -398,7 +398,13 @@ -(void)setStore:(id)store success:(void (^)(void))onStoreDataReady fail // Check if the user has enabled crypto MXWeakify(self); - [MXLegacyCrypto checkCryptoWithMatrixSession:self complete:^(id crypto, NSError *error) { + [MXLegacyCrypto initializeCryptoWithMatrixSession:self migrationProgress:^(double progress) { + if (MXSDKOptions.sharedInstance.enableStartupProgress) + { + [self.startupProgress updateMigrationProgress:progress]; + } + + } complete:^(id crypto, NSError *error) { MXStrongifyAndReturnIfNil(self); if (!crypto && error) diff --git a/MatrixSDK/MXSessionStartupProgress.swift b/MatrixSDK/MXSessionStartupProgress.swift index 9725888e5d..abe702d5cb 100644 --- a/MatrixSDK/MXSessionStartupProgress.swift +++ b/MatrixSDK/MXSessionStartupProgress.swift @@ -74,6 +74,11 @@ public protocol MXSessionStartupProgressDelegate: AnyObject { } } + /// Update the progress of the `migratingData` stage + @objc public func updateMigrationProgress(_ progress: Double) { + stage = .migratingData(progress: progress) + } + /// Increment the total number of sync attempts during the `serverSyncing` stage @objc public func incrementSyncAttempt() { guard stage == nil || stage?.isSyncing == true else { diff --git a/MatrixSDK/Utils/Logs/MXLog.swift b/MatrixSDK/Utils/Logs/MXLog.swift index 1fed99dcf9..c36bbc446d 100644 --- a/MatrixSDK/Utils/Logs/MXLog.swift +++ b/MatrixSDK/Utils/Logs/MXLog.swift @@ -235,6 +235,10 @@ struct MXNamedLog { logger.debug(formattedMessage(message, function: function), file, function, line: line) } + func warning(_ message: String, file: String = #file, function: String = #function, line: Int = #line) { + logger.warning(formattedMessage(message, function: function), file, function, line: line) + } + /// Logging errors requires a static message, all other details must be sent as additional parameters func error(_ message: StaticString, context: Any? = nil, file: String = #file, function: String = #function, line: Int = #line) { logger.error(formattedMessage(message, function: function), file, function, line: line, context: context) diff --git a/MatrixSDKTests/Crypto/Algorithms/RoomEvents/MXRoomEventDecryptionUnitTests.swift b/MatrixSDKTests/Crypto/Algorithms/RoomEvents/MXRoomEventDecryptionUnitTests.swift index 99a7937577..0ad8fa354f 100644 --- a/MatrixSDKTests/Crypto/Algorithms/RoomEvents/MXRoomEventDecryptionUnitTests.swift +++ b/MatrixSDKTests/Crypto/Algorithms/RoomEvents/MXRoomEventDecryptionUnitTests.swift @@ -39,12 +39,12 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { } } - var decryptor: DecryptorStub! - var roomDecryptor: MXRoomEventDecryption! + var handler: DecryptorStub! + var decryptor: MXRoomEventDecryption! override func setUp() { - decryptor = DecryptorStub() - roomDecryptor = MXRoomEventDecryption(handler: decryptor) + handler = DecryptorStub() + decryptor = MXRoomEventDecryption(handler: handler) } // MARK: - Decrypt @@ -57,11 +57,11 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { id: "1", sessionId: "123" ) - decryptor.stubbedEvents = [ + handler.stubbedEvents = [ "1": .stub(clearEvent: plain) ] - let results = await roomDecryptor.decrypt(events: [event]) + let results = await decryptor.decrypt(events: [event]) XCTAssertEqual(results.first?.clearEvent as? [String: String], plain) } @@ -85,11 +85,11 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { ) ] - decryptor.stubbedEvents = [ + handler.stubbedEvents = [ "2": .stub(clearEvent: plain) ] - let results = await roomDecryptor.decrypt(events: events) + let results = await decryptor.decrypt(events: events) XCTAssertEqual(results.count, 3) XCTAssertNotNil(results[0].error) @@ -103,7 +103,7 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { let events = await prepareEventsForRedecryption() let invalidEvent = MXEvent.fixture(id: 123) - await roomDecryptor.handlePossibleRoomKeyEvent(invalidEvent) + await decryptor.handlePossibleRoomKeyEvent(invalidEvent) await waitForDecryption() XCTAssertNil(events[0].clear) @@ -115,7 +115,7 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { let events = await prepareEventsForRedecryption() let roomKey = MXEvent.roomKeyFixture(sessionId: "123") - await roomDecryptor.handlePossibleRoomKeyEvent(roomKey) + await decryptor.handlePossibleRoomKeyEvent(roomKey) await waitForDecryption() XCTAssertNotNil(events[0].clear) @@ -127,7 +127,7 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { let events = await prepareEventsForRedecryption() let roomKey = MXEvent.forwardedRoomKeyFixture(sessionId: "123") - await roomDecryptor.handlePossibleRoomKeyEvent(roomKey) + await decryptor.handlePossibleRoomKeyEvent(roomKey) await waitForDecryption() XCTAssertNotNil(events[0].clear) @@ -140,7 +140,7 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { func test_retryUndecryptedEvents() async { let events = await prepareEventsForRedecryption() - await roomDecryptor.retryUndecryptedEvents(sessionIds: ["123", "456"]) + await decryptor.retryUndecryptedEvents(sessionIds: ["123", "456"]) await waitForDecryption() XCTAssertNotNil(events[0].clear) @@ -173,7 +173,7 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { // Attempt to decrypt these events, which will produce errors // and add them to an internal undecrypted events cache - let results = await roomDecryptor.decrypt(events: events) + let results = await decryptor.decrypt(events: events) for (event, result) in zip(events, results) { event.setClearData(result) } @@ -181,7 +181,7 @@ class MXRoomEventDecryptionUnitTests: XCTestCase { // Now stub out decryption result so that if these events are decrypted again // we get the correct result let decrypted = DecryptedEvent.stub(clearEvent: ["type": "m.decrypted"]) - decryptor.stubbedEvents = [ + handler.stubbedEvents = [ "1": decrypted, "2": decrypted, "3": decrypted diff --git a/MatrixSDKTests/Crypto/Algorithms/RoomEvents/MXRoomEventEncryptionUnitTests.swift b/MatrixSDKTests/Crypto/Algorithms/RoomEvents/MXRoomEventEncryptionUnitTests.swift new file mode 100644 index 0000000000..cffc1884f9 --- /dev/null +++ b/MatrixSDKTests/Crypto/Algorithms/RoomEvents/MXRoomEventEncryptionUnitTests.swift @@ -0,0 +1,285 @@ +// +// 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 +@testable import MatrixSDK + +#if DEBUG + +import MatrixSDKCrypto + +class MXRoomEventEncryptionUnitTests: XCTestCase { + class StateStub: MXRoomState { + var stubbedAlgorithm: String? = kMXCryptoMegolmAlgorithm + override var encryptionAlgorithm: String! { + return stubbedAlgorithm + } + + var stubbedHistoryVisibility: MXRoomHistoryVisibility? + override var __historyVisibility: String? { + return stubbedHistoryVisibility?.identifier + } + } + + class MembersStub: MXRoomMembers { + var stubbedMembers: [MemberStub]? + override var members: [MXRoomMember]! { + return stubbedMembers + } + + var eligibleUsers = Set() + override func encryptionTargetMembers(_ historyVisibility: String!) -> [MXRoomMember]! { + return stubbedMembers?.filter { + eligibleUsers.contains($0.userId) + } + } + } + + class MemberStub: MXRoomMember { + var stubbedUserId: String? + override var userId: String! { + stubbedUserId + } + + init(userId: String) { + self.stubbedUserId = userId + super.init() + } + } + + class RoomStub: MXRoom { + var isEncrypted: Bool = true + override var summary: MXRoomSummary! { + let summary = MXRoomSummary() + summary.isEncrypted = isEncrypted + return summary + } + + var stubbedState: StateStub? + override func state(_ onComplete: ((MXRoomState?) -> Void)!) { + onComplete(stubbedState) + } + + var stubbedMembers: MembersStub? + override func members( + _ success: ((MXRoomMembers?) -> Void)!, + lazyLoadedMembers: ((MXRoomMembers?) -> Void)!, + failure: ((Swift.Error?) -> Void)! + ) -> MXHTTPOperation! { + success?(stubbedMembers) + return MXHTTPOperation() + } + } + + class EncryptorStub: CryptoIdentityStub, MXCryptoRoomEventEncrypting { + var trackedUsers: Set = [] + func addTrackedUsers(_ users: [String]) { + trackedUsers = trackedUsers.union(users) + } + + var sharedUsers = [String]() + var sharedSettings: EncryptionSettings? + func shareRoomKeysIfNecessary(roomId: String, users: [String], settings: EncryptionSettings) async throws { + sharedUsers = users + sharedSettings = settings + } + + func encryptRoomEvent(content: [AnyHashable: Any], roomId: String, eventType: String) throws -> [String: Any] { + return [ + // Simulate encryption by moving content to `ciphertext` + "ciphertext": content + ] + } + + func discardRoomKey(roomId: String) { + } + } + + var handler: EncryptorStub! + var store: MXMemoryCryptoStore! + var encryptor: MXRoomEventEncryption! + var roomId = "ABC" + var room: RoomStub! + var state: StateStub! + var members: MembersStub! + + override func setUp() { + handler = EncryptorStub() + store = MXMemoryCryptoStore(credentials: MXCredentials()) + room = .init(roomId: roomId, andMatrixSession: nil) + state = .init(roomId: roomId, andMatrixSession: nil, andDirection: true) + members = .init() + + room.stubbedState = state + room.stubbedMembers = members + + encryptor = MXRoomEventEncryption( + handler: handler, + legacyStore: store, + getRoomAction: { id in + id == self.room.roomId ? self.room : nil + } + ) + } + + // MARK: - Is encrypted + + func test_isRoomEncrypted() { + XCTAssertFalse(encryptor.isRoomEncrypted(roomId: roomId)) + store.storeAlgorithm(forRoom: roomId, algorithm: "megolm") + XCTAssertTrue(encryptor.isRoomEncrypted(roomId: roomId)) + } + + // MARK: - Ensure keys + + func test_ensureRoomKeysShared_throwsForMissingRoom() async { + do { + try await encryptor.ensureRoomKeysShared(roomId: "unknown") + XCTFail("Should not succeed") + } catch { + XCTAssertNotNil(error) + } + } + + func test_ensureRoomKeysShared_skipForUnencryptedRooms() async throws { + room.isEncrypted = false + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTAssertNil(handler.sharedSettings) + } + + func test_ensureRoomKeysShared_correctEncryptionAlgorithm() async throws { + XCTAssertNil(store.algorithm(forRoom: roomId)) + + // No algorithm -> throws + nothing stored + state.stubbedAlgorithm = nil + do { + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTFail("Should not succeed") + } catch { + XCTAssertNil(store.algorithm(forRoom: roomId)) + } + + // Invalid algorithm -> throws + nothing stored + state.stubbedAlgorithm = "blabla" + do { + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTFail("Should not succeed") + } catch { + XCTAssertNil(store.algorithm(forRoom: roomId)) + } + + // Valid -> algorithm stored + state.stubbedAlgorithm = kMXCryptoMegolmAlgorithm + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTAssertEqual(store.algorithm(forRoom: roomId), kMXCryptoMegolmAlgorithm) + + // Another invalid algorithm -> throws + previous algorithm stored + state.stubbedAlgorithm = "blabla" + do { + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTFail("Should not succeed") + } catch { + XCTAssertEqual(store.algorithm(forRoom: roomId), kMXCryptoMegolmAlgorithm) + } + + // Another valid -> succeeds + state.stubbedAlgorithm = kMXCryptoMegolmAlgorithm + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTAssertEqual(store.algorithm(forRoom: roomId), kMXCryptoMegolmAlgorithm) + } + + func test_ensureRoomKeysShared_correctSettings() async throws { + try await encryptor.ensureRoomKeysShared(roomId: roomId) + + let settings = handler.sharedSettings + XCTAssertEqual(settings?.algorithm, .megolmV1AesSha2) + XCTAssertEqual(settings?.rotationPeriod, 7 * 24 * 3600) + XCTAssertEqual(settings?.rotationPeriodMsgs, 100) + } + + func test_ensureRoomKeysShared_correctHistoryVisibility() async throws { + let stateToSettings: [(MXRoomHistoryVisibility?, HistoryVisibility)] = [ + (nil, .joined), + (.worldReadable, .worldReadable), + (.invited, .invited), + (.joined, .joined), + (.shared, .shared), + ] + + for (state, settings) in stateToSettings { + room.stubbedState?.stubbedHistoryVisibility = state + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTAssertEqual(handler.sharedSettings?.historyVisibility, settings) + } + } + + func test_ensureRoomKeysShared_correctAllowTrustedDevices() async throws { + let storeToSettings: [((Bool, Bool), Bool)] = [ + ((false, false), false), + ((true, false), true), + ((false, true), true), + ((true, true), true), + ] + + for ((global, perRoom), settings) in storeToSettings { + store.globalBlacklistUnverifiedDevices = global + store.storeBlacklistUnverifiedDevices(inRoom: roomId, blacklist: perRoom) + + try await encryptor.ensureRoomKeysShared(roomId: roomId) + + XCTAssertEqual(handler.sharedSettings?.onlyAllowTrustedDevices, settings) + } + } + + func test_ensureRoomKeysShared_correctEligibleUsers() async throws { + members.stubbedMembers = [ + .init(userId: "Alice"), + .init(userId: "Bob"), + .init(userId: "Carol"), + ] + members.eligibleUsers = ["Alice", "Carol"] + + try await encryptor.ensureRoomKeysShared(roomId: roomId) + XCTAssertEqual(handler.sharedUsers, ["Alice", "Carol"]) + } + + // MARK: - Encrypt + + func test_encrypt_ensuresEncryptionAndKeys() async throws { + XCTAssertFalse(encryptor.isRoomEncrypted(roomId: roomId)) + + _ = try await encryptor.encrypt( + content: ["body": "Hello"], + eventType: kMXEventTypeStringRoomMessage, + in: room + ) + + XCTAssertNotNil(handler.sharedSettings) + XCTAssertEqual(store.algorithm(forRoom: roomId), kMXCryptoMegolmAlgorithm) + } + + func test_encrypt_returnsEncryptedContent() async throws { + let result = try await encryptor.encrypt( + content: ["body": "Hello"], + eventType: kMXEventTypeStringRoomMessage, + in: room + ) + + XCTAssertEqual(result["ciphertext"] as? [String: String], ["body": "Hello"]) + } +} + +#endif diff --git a/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift index 7febe3a9e3..c09fb0ac79 100644 --- a/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift +++ b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift @@ -209,8 +209,12 @@ public class MXMemoryCryptoStore: NSObject, MXCryptoStore { Array(olmSessions.filter { $0.key.deviceKey == deviceKey }.values) } - public func sessions() -> [MXOlmSession]! { - Array(olmSessions.values) + public func enumerateSessions(by batchSize: Int, block: (([MXOlmSession]?, Double) -> Void)!) { + block(Array(olmSessions.values), 1) + } + + public func sessionsCount() -> UInt { + UInt(olmSessions.count) } // MARK: - Inbound Group Sessions @@ -231,6 +235,11 @@ public class MXMemoryCryptoStore: NSObject, MXCryptoStore { public func inboundGroupSessions() -> [MXOlmInboundGroupSession]! { inboundSessions.map { $0.session } } + + public func enumerateInboundGroupSessions(by batchSize: Int, block: (([MXOlmInboundGroupSession]?, Set?, Double) -> Void)!) { + let backedUp = inboundSessions.filter { $0.backedUp }.map(\.sessionId) + block(inboundGroupSessions(), Set(backedUp), 1) + } public func inboundGroupSessions(withSessionId sessionId: String!) -> [MXOlmInboundGroupSession]! { inboundSessions.filter { $0.sessionId == sessionId }.map { $0.session } diff --git a/MatrixSDKTests/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStoreTests.swift b/MatrixSDKTests/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStoreTests.swift new file mode 100644 index 0000000000..303670f7fe --- /dev/null +++ b/MatrixSDKTests/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStoreTests.swift @@ -0,0 +1,114 @@ +// +// 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 +@testable import MatrixSDK + +class MXRealmCryptoStoreTests: XCTestCase { + var store: MXRealmCryptoStore! + override func setUp() { + store = MXRealmCryptoStore() + } + + override func tearDown() { + MXRealmCryptoStore.deleteAllStores() + } + + func makeSession( + deviceKey: String = "XYZ" + ) -> MXOlmSession { + return MXOlmSession(olmSession: OLMSession(), deviceKey: deviceKey) + } + + func makeGroupSession( + roomId: String = "ABC", + senderKey: String? = "Bob", + isUntrusted: Bool = false, + backedUp: Bool = false + ) -> MXOlmInboundGroupSession { + let device = MXOlmDevice(store: store)! + 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 + return session + } + + // MARK: - Olm sessions + + func test_saveAndLoadSession() { + let session = makeSession() + + store.store(session) + XCTAssertEqual(store.sessionsCount(), 1) + + let fetched = store.sessions(withDevice: "XYZ") + XCTAssertEqual(fetched?.count, 1) + } + + func test_enumerateSessions() { + for i in 0 ..< 15 { + let session = makeSession(deviceKey: "\(i)") + store.store(session) + } + + XCTAssertEqual(store.sessionsCount(), 15) + + var count = 0 + var batches = 0 + store.enumerateSessions(by: 4) { sessions, _ in + count += sessions?.count ?? 0 + batches += 1 + } + + XCTAssertEqual(count, 15) + XCTAssertEqual(batches, 4) + } + + // MARK: - Megolm sessions + + func test_saveAndLoadGroupSession() { + let session = makeGroupSession() + + store.store([session]) + XCTAssertEqual(store.inboundGroupSessionsCount(false), 1) + + let fetched = store.inboundGroupSessions() + XCTAssertEqual(fetched?.count, 1) + } + + func test_enumerateGroupSessions() { + for _ in 0 ..< 111 { + let session = makeGroupSession() + store.store([session]) + } + + XCTAssertEqual(store.inboundGroupSessionsCount(false), 111) + + var count = 0 + var batches = 0 + store.enumerateInboundGroupSessions(by: 20) { sessions, backedUp, progress in + count += sessions?.count ?? 0 + batches += 1 + } + + XCTAssertEqual(count, 111) + XCTAssertEqual(batches, 6) + } +} diff --git a/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift b/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift index 6cae30420a..2861c2e21d 100644 --- a/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift +++ b/MatrixSDKTests/Crypto/Migration/Data/MXCryptoMigrationStoreUnitTests.swift @@ -47,6 +47,22 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { try store.extractData(with: pickleKey ?? self.pickleKey) } + func extractSessions(pickleKey: Data? = nil) throws -> [PickledSession] { + var sessions = [PickledSession]() + store.extractSessions(with: pickleKey ?? self.pickleKey, batchSize: .max) { batch, progress in + sessions += batch + } + return sessions + } + + func extractGroupSessions(pickleKey: Data? = nil) throws -> [PickledInboundGroupSession] { + var sessions = [PickledInboundGroupSession]() + store.extractGroupSessions(with: pickleKey ?? self.pickleKey, batchSize: .max) { batch, progress in + sessions += batch + } + return sessions + } + @discardableResult func storeGroupSession( roomId: String = "ABC", @@ -106,8 +122,11 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { legacyStore.store(session) let pickle = try session.session.serializeData(withKey: pickleKey) - let sessions = try extractData().sessions + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().sessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractSessions() XCTAssertEqual(sessions.count, 1) XCTAssertEqual(sessions[0].pickle, pickle) XCTAssertEqual(sessions[0].senderKey, "XYZ") @@ -116,22 +135,27 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { XCTAssertEqual(sessions[0].lastUseTime, "123") } - func test_extractsMultipleSession() throws { - for i in 0 ..< 3 { + func test_extractsMultipleSessionsInBatches() throws { + for i in 0 ..< 12 { legacyStore.store(MXOlmSession(olmSession: OLMSession(), deviceKey: "\(i)")) } - let sessions = try extractData().sessions - - XCTAssertEqual(sessions.count, 3) + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().sessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractSessions() + XCTAssertEqual(sessions.count, 12) } func test_extractsGroupSession() throws { let session = storeGroupSession(roomId: "abcd") let pickle = try session.session.serializeData(withKey: pickleKey) - let sessions = try extractData().inboundGroupSessions + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().inboundGroupSessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 1) XCTAssertEqual(sessions[0].pickle, pickle) XCTAssertEqual(sessions[0].senderKey, "Bob") @@ -146,8 +170,11 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { storeGroupSession(senderKey: isValid ? "Bob" : nil) } - let sessions = try extractData().inboundGroupSessions + // There are no sessions in the general migration data + XCTAssertTrue(try extractData().inboundGroupSessions.isEmpty) + // But they can be accumulated by batching + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 2) } @@ -156,7 +183,7 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { storeGroupSession(isUntrusted: false) storeGroupSession(isUntrusted: false) - let sessions = try extractData().inboundGroupSessions + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 3) XCTAssertTrue(sessions[0].imported) @@ -169,7 +196,7 @@ class MXCryptoMigrationStoreUnitTests: XCTestCase { storeGroupSession(backedUp: true) storeGroupSession(backedUp: false) - let sessions = try extractData().inboundGroupSessions + let sessions = try extractGroupSessions() XCTAssertEqual(sessions.count, 3) XCTAssertFalse(sessions[0].backedUp) diff --git a/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift b/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift index e79c80d1f9..c1c5489dc0 100644 --- a/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift +++ b/MatrixSDKTests/Crypto/Migration/MXCryptoMigrationV2Tests.swift @@ -61,7 +61,7 @@ class MXCryptoMigrationV2Tests: XCTestCase { MXKeyProvider.sharedInstance().delegate = KeyProvider() let migration = MXCryptoMigrationV2(legacyStore: store) - try migration.migrateCrypto() + try migration.migrateCrypto { _ in } MXKeyProvider.sharedInstance().delegate = nil return try MXCryptoMachine( diff --git a/MatrixSDKTests/TestPlans/UnitTests.xctestplan b/MatrixSDKTests/TestPlans/UnitTests.xctestplan index e41e043294..ef3434c5b9 100644 --- a/MatrixSDKTests/TestPlans/UnitTests.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTests.xctestplan @@ -80,6 +80,7 @@ "MXReplyEventParserUnitTests", "MXResponseUnitTests", "MXRoomEventDecryptionUnitTests", + "MXRoomEventEncryptionUnitTests", "MXRoomEventFilterUnitTests", "MXRoomKeyEventContentUnitTests", "MXRoomKeyInfoFactoryUnitTests", diff --git a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan index 913c8a271f..e0c6681590 100644 --- a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan @@ -88,6 +88,7 @@ "MXReplyEventParserUnitTests", "MXResponseUnitTests", "MXRoomEventDecryptionUnitTests", + "MXRoomEventEncryptionUnitTests", "MXRoomEventFilterUnitTests", "MXRoomKeyEventContentUnitTests", "MXRoomKeyInfoFactoryUnitTests", diff --git a/changelog.d/pr-1687.change b/changelog.d/pr-1687.change new file mode 100644 index 0000000000..e1fba6c767 --- /dev/null +++ b/changelog.d/pr-1687.change @@ -0,0 +1 @@ +CryptoV2: Batch migrate olm and megolm sessions diff --git a/changelog.d/pr-1689.change b/changelog.d/pr-1689.change new file mode 100644 index 0000000000..18ec47e9ab --- /dev/null +++ b/changelog.d/pr-1689.change @@ -0,0 +1 @@ +CryptoV2: Extract room event encryption