CVE-2022-39257 (published 2022-09-28T21:15Z)

Matrix iOS SDK allows developers to build iOS apps compatible with Matrix. Prior to version 0.23.19, an attacker cooperating with a malicious homeserver can construct messages appearing to have come from another person. Such messages will be marked with a grey shield on some platforms, but this may be missing in others. This attack is possible due to the matrix-ios-sdk implementing a too permissive key forwarding strategy. The default policy for accepting key forwards has been made more strict in the matrix-ios-sdk version 0.23.19. matrix-ios-sdk will now only accept forwarded keys in response to previously issued requests and only from own, verified devices. The SDK now sets a `trusted` flag on the decrypted message upon decryption, based on whether the key used to decrypt the message was received from a trusted source. Clients need to ensure that messages decrypted with a key with `trusted = false` are decorated appropriately (for example, by showing a warning for such messages). This attack requires coordination between a malicious home server and an attacker, so those who trust their home servers do not need a workaround.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-287
cwe_name: Improper Authentication
description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
extended_description: 
            
			
         
         

Fix information:
repo_url: https://github.com/matrix-org/matrix-ios-sdk
commit_details: repo_url: https://github.com/matrix-org/matrix-ios-sdk
author: Andy Uhnak
author_date: 2022-09-28 15:25:36+01:00
msg: Resolve multiple CVEs

CVE-2022-39255
CVE-2022-39257
repository: repo_url: https://github.com/matrix-org/matrix-ios-sdk
repo_name: matrix-org/matrix-ios-sdk
description: The Matrix SDK for iOS
repo_language: Objective-C
owner: matrix-org
file_changes: filename: CHANGES.md
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,10 @@
+## Changes in 0.23.19 (2022-09-28)
+
+🐛 Bugfixes
+
+- CVE-2022-39255: Olm/Megolm protocol confusion ([Security advisory](https://github.com/matrix-org/matrix-ios-sdk/security/advisories/GHSA-hw6g-j8v6-9hcm))
+- CVE-2022-39257: Impersonation via forwarded Megolm sessions ([Security advisory](https://github.com/matrix-org/matrix-ios-sdk/security/advisories/GHSA-qxr3-5jmq-xcf4))
+
 ## Changes in 0.23.18 (2022-09-07)
 
 ✨ Features

programming_language: Markdown
filename: project.pbxproj
change_type: ModificationType.MODIFY
diff: @@ -1801,6 +1801,14 @@
 		ECF29BDF264195320053E6D6 /* MXAssertedIdentityModel.h in Headers */ = {isa = PBXBuildFile; fileRef = ECF29BDD264195320053E6D6 /* MXAssertedIdentityModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		ECF29BE52641953C0053E6D6 /* MXAssertedIdentityModel.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF29BE42641953C0053E6D6 /* MXAssertedIdentityModel.m */; };
 		ECF29BE62641953C0053E6D6 /* MXAssertedIdentityModel.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF29BE42641953C0053E6D6 /* MXAssertedIdentityModel.m */; };
+		ED01915228C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */; };
+		ED01915328C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */; };
+		ED01915428C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		ED01915528C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		ED01915628C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */; };
+		ED01915728C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */; };
+		ED01915828C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		ED01915928C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */ = {isa = PBXBuildFile; fileRef = ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		ED1AE92A2881AC7500D3432A /* MXWarnings.h in Headers */ = {isa = PBXBuildFile; fileRef = ED1AE9292881AC7100D3432A /* MXWarnings.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		ED1AE92B2881AC7500D3432A /* MXWarnings.h in Headers */ = {isa = PBXBuildFile; fileRef = ED1AE9292881AC7100D3432A /* MXWarnings.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		ED21F68528104DA2002FF83D /* MXMegolmEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED21F68428104DA2002FF83D /* MXMegolmEncryptionTests.swift */; };
@@ -1846,6 +1854,28 @@
 		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 */; };
+		ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */; };
+		ED6DABFD28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */; };
+		ED6DAC0228C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */; };
+		ED6DAC0328C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */; };
+		ED6DAC0728C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */; };
+		ED6DAC0828C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */; };
+		ED6DAC0A28C784AE00ECDCB6 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */; };
+		ED6DAC0B28C784AE00ECDCB6 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */; };
+		ED6DAC0D28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */; };
+		ED6DAC0E28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */; };
+		ED6DAC1128C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */; };
+		ED6DAC1228C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */; };
+		ED6DAC1528C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */; };
+		ED6DAC1628C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */; };
+		ED6DAC1828C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */; };
+		ED6DAC1928C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */; };
+		ED6DAC1B28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */; };
+		ED6DAC1C28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */; };
+		ED6DAC1E28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */; };
+		ED6DAC1F28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */; };
+		ED6DAC2128C7A51400ECDCB6 /* MXDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */; };
+		ED6DAC2228C7A51400ECDCB6 /* MXDateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */; };
 		ED7019DD2886C24100FC31B9 /* MXCrossSigningInfoSourceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D242885A39800F897E7 /* MXCrossSigningInfoSourceUnitTests.swift */; };
 		ED7019DE2886C24A00FC31B9 /* MXTrustLevelSourceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D2F2885AB0300F897E7 /* MXTrustLevelSourceUnitTests.swift */; };
 		ED7019DF2886C25600FC31B9 /* MXDeviceInfoUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D1B2885909E00F897E7 /* MXDeviceInfoUnitTests.swift */; };
@@ -2910,6 +2940,10 @@
 		ECF29BD2264194BB0053E6D6 /* MXCallAssertedIdentityEventContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXCallAssertedIdentityEventContent.m; sourceTree = "<group>"; };
 		ECF29BDD264195320053E6D6 /* MXAssertedIdentityModel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXAssertedIdentityModel.h; sourceTree = "<group>"; };
 		ECF29BE42641953C0053E6D6 /* MXAssertedIdentityModel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXAssertedIdentityModel.m; sourceTree = "<group>"; };
+		ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXForwardedRoomKeyEventContent.m; sourceTree = "<group>"; };
+		ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomKeyEventContent.h; sourceTree = "<group>"; };
+		ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXRoomKeyEventContent.m; sourceTree = "<group>"; };
+		ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXForwardedRoomKeyEventContent.h; sourceTree = "<group>"; };
 		ED1AE9292881AC7100D3432A /* MXWarnings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXWarnings.h; sourceTree = "<group>"; };
 		ED21F68428104DA2002FF83D /* MXMegolmEncryptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXMegolmEncryptionTests.swift; sourceTree = "<group>"; };
 		ED2DD111286C450600F06731 /* MXCryptoMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoMachine.swift; sourceTree = "<group>"; };
@@ -2935,6 +2969,17 @@
 		ED5C753A28B3E80300D24E85 /* MXLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXLogger.m; sourceTree = "<group>"; };
 		ED5C753B28B3E80300D24E85 /* MXLogObjcWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXLogObjcWrapper.h; sourceTree = "<group>"; };
 		ED5C95CD2833E85600843D82 /* MXOlmDeviceUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXOlmDeviceUnitTests.swift; sourceTree = "<group>"; };
+		ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfoFactory.swift; sourceTree = "<group>"; };
+		ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfo.swift; sourceTree = "<group>"; };
+		ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXForwardedRoomKeyEventContentUnitTests.swift; sourceTree = "<group>"; };
+		ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = "<group>"; };
+		ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyEventContentUnitTests.swift; sourceTree = "<group>"; };
+		ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyInfoFactoryUnitTests.swift; sourceTree = "<group>"; };
+		ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXMemoryCryptoStore.swift; sourceTree = "<group>"; };
+		ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomKeyResult.swift; sourceTree = "<group>"; };
+		ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXUnrequestedForwardedRoomKeyManager.swift; sourceTree = "<group>"; };
+		ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXUnrequestedForwardedRoomKeyManagerUnitTests.swift; sourceTree = "<group>"; };
+		ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXDateProvider.swift; sourceTree = "<group>"; };
 		ED7019E42886C32900FC31B9 /* MXSASTransactionV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSASTransactionV2.swift; sourceTree = "<group>"; };
 		ED7019E72886C33100FC31B9 /* MXKeyVerificationRequestV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXKeyVerificationRequestV2.swift; sourceTree = "<group>"; };
 		ED7019EA2886C33A00FC31B9 /* MXKeyVerificationManagerV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXKeyVerificationManagerV2.swift; sourceTree = "<group>"; };
@@ -3229,6 +3274,7 @@
 				B17B2BDB2369FC81009D6650 /* MXUIKitBackgroundTask.m */,
 				EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */,
 				ED1AE9292881AC7100D3432A /* MXWarnings.h */,
+				ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */,
 			);
 			path = Utils;
 			sourceTree = "<group>";
@@ -3288,6 +3334,7 @@
 				32261B8723C749FB0018F1E2 /* Trust */,
 				32A1513B1DAF768D00400192 /* Data */,
 				3AC1379326432ED500EE1E74 /* Dehydration */,
+				ED6DAC0428C771D500ECDCB6 /* RoomKeys */,
 				32BBAE642178E99100D85F46 /* KeyBackup */,
 				32FA10B21FA1C28100E54233 /* KeySharing */,
 				3A108A382580E979005EEBE9 /* KeyProvider */,
@@ -4318,6 +4365,7 @@
 				32FA10BF1FA1C9EE00E54233 /* MXOutgoingRoomKeyRequestManager.h */,
 				32FA10C01FA1C9EE00E54233 /* MXOutgoingRoomKeyRequestManager.m */,
 				32A30B161FB4813400C8309E /* MXIncomingRoomKeyRequestManager.h */,
+				ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */,
 				32A30B171FB4813400C8309E /* MXIncomingRoomKeyRequestManager.m */,
 				ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */,
 				EDBCF335281A8AB900ED5044 /* MXSharedHistoryKeyService.h */,
@@ -4329,6 +4377,10 @@
 		32FA10C31FA1C9F700E54233 /* Data */ = {
 			isa = PBXGroup;
 			children = (
+				ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */,
+				ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */,
+				ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */,
+				ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */,
 				32F945F21FAB83D900622468 /* MXIncomingRoomKeyRequest.h */,
 				32F945F31FAB83D900622468 /* MXIncomingRoomKeyRequest.m */,
 				32F945F41FAB83D900622468 /* MXIncomingRoomKeyRequestCancellation.h */,
@@ -5127,6 +5179,7 @@
 				ED8F1D1428857FD300F897E7 /* CrossSigning */,
 				ED8F1D2E2885AAEB00F897E7 /* Trust */,
 				ED8F1D292885A7DF00F897E7 /* Devices */,
+				ED6DAC0F28C7889A00ECDCB6 /* RoomKeys */,
 				ED44F01628180F1300452A5D /* KeySharing */,
 				ED35652A281150230002BF6A /* Data */,
 				ED21F67B28104BA1002FF83D /* Algorithms */,
@@ -5178,6 +5231,7 @@
 		ED35652A281150230002BF6A /* Data */ = {
 			isa = PBXGroup;
 			children = (
+				ED6DAC1328C78D3700ECDCB6 /* Store */,
 				ED35652B281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift */,
 				ED35652E281153480002BF6A /* MXMegolmSessionDataUnitTests.swift */,
 			);
@@ -5187,7 +5241,9 @@
 		ED44F01628180F1300452A5D /* KeySharing */ = {
 			isa = PBXGroup;
 			children = (
+				ED6DAC0528C77E0600ECDCB6 /* Data */,
 				ED44F01728180F1C00452A5D /* MXSharedHistoryKeyManagerUnitTests.swift */,
+				ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */,
 			);
 			path = KeySharing;
 			sourceTree = "<group>";
@@ -5206,6 +5262,41 @@
 			path = Logs;
 			sourceTree = "<group>";
 		};
+		ED6DAC0428C771D500ECDCB6 /* RoomKeys */ = {
+			isa = PBXGroup;
+			children = (
+				ED6DAC0128C76F0A00ECDCB6 /* MXRoomKeyInfo.swift */,
+				ED6DAC1728C799E300ECDCB6 /* MXRoomKeyResult.swift */,
+				ED6DABFB28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift */,
+			);
+			path = RoomKeys;
+			sourceTree = "<group>";
+		};
+		ED6DAC0528C77E0600ECDCB6 /* Data */ = {
+			isa = PBXGroup;
+			children = (
+				ED6DAC0628C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift */,
+				ED6DAC0C28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift */,
+			);
+			path = Data;
+			sourceTree = "<group>";
+		};
+		ED6DAC0F28C7889A00ECDCB6 /* RoomKeys */ = {
+			isa = PBXGroup;
+			children = (
+				ED6DAC1028C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift */,
+			);
+			path = RoomKeys;
+			sourceTree = "<group>";
+		};
+		ED6DAC1328C78D3700ECDCB6 /* Store */ = {
+			isa = PBXGroup;
+			children = (
+				ED6DAC1428C78D4000ECDCB6 /* MXMemoryCryptoStore.swift */,
+			);
+			path = Store;
+			sourceTree = "<group>";
+		};
 		ED7019ED2886CA6C00FC31B9 /* Verification */ = {
 			isa = PBXGroup;
 			children = (
@@ -5365,6 +5456,7 @@
 			isa = PBXGroup;
 			children = (
 				3259D0072603705300C365DB /* Array.swift */,
+				ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */,
 				F08B8D5A1E014711006171A8 /* NSData+MatrixSDK.h */,
 				F08B8D5B1E014711006171A8 /* NSData+MatrixSDK.m */,
 				3259D02226037A7200C365DB /* NSDictionary.swift */,
@@ -5704,6 +5796,7 @@
 				326056851C76FDF2009D44AD /* MXRoomEventTimeline.h in Headers */,
 				3A59A49D25A7A16F00DDA1FC /* MXOlmOutboundGroupSession.h in Headers */,
 				32AF927F240EA0190008A0FD /* MXSecretShareManager.h in Headers */,
+				ED01915828C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */,
 				321CFDEE225264C4004D31DF /* NSArray+MatrixSDK.h in Headers */,
 				1838928827031D1D003F0C4F /* MXSendReplyEventStringLocalizerProtocol.h in Headers */,
 				B11BD44822CB56790064D8B0 /* MXReplyEventParser.h in Headers */,
@@ -5731,6 +5824,7 @@
 				32DC15D01A8CF7AE006F9AD3 /* MXNotificationCenter.h in Headers */,
 				3275FD9C21A6B60B00B9C13D /* MXLoginPolicy.h in Headers */,
 				EC05473425FF8A3C0047ECD7 /* MXVirtualRoomInfo.h in Headers */,
+				ED01915428C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */,
 				F0173EAC1FCF0E8900B5F6A3 /* MXGroup.h in Headers */,
 				32D2CC0523422462002BD8CA /* MX3PidAddSession.h in Headers */,
 				329FB17F1A0B665800A5E88E /* MXUser.h in Headers */,
@@ -6008,6 +6102,7 @@
 				B14EF3202397E90400758AF0 /* MXKeyBackupPassword.h in Headers */,
 				B14EF3212397E90400758AF0 /* MXRestClient.h in Headers */,
 				324DD2B2246BDC6800377005 /* MXSecretStorage_Private.h in Headers */,
+				ED01915928C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */,
 				32B0E33A23A2989A0054FF1A /* MXEventReferenceChunk.h in Headers */,
 				B1EE98C72804697400AB63F0 /* MXBeacon.h in Headers */,
 				B14EF3222397E90400758AF0 /* MXKeyVerificationManager.h in Headers */,
@@ -6053,6 +6148,7 @@
 				EC60EE07265CFFF400B39A4E /* MXGroupSyncProfile.h in Headers */,
 				EC11658E270F3ABF0089FA56 /* RLMRealm+MatrixSDK.h in Headers */,
 				324AAC7E2399143400380A66 /* MXKeyVerificationCancel.h in Headers */,
+				ED01915528C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */,
 				B14EF3372397E90400758AF0 /* MXRoomTombStoneContent.h in Headers */,
 				3274538B23FD918800438328 /* MXKeyVerificationByToDeviceRequest.h in Headers */,
 				B14EF3382397E90400758AF0 /* MXFilterObject.h in Headers */,
@@ -6470,6 +6566,7 @@
 				3275FD9D21A6B60B00B9C13D /* MXLoginPolicy.m in Sources */,
 				327F8DB31C6112BA00581CA3 /* MXRoomThirdPartyInvite.m in Sources */,
 				B17982FC2119E4A2001FD722 /* MXRoomPowerLevels.m in Sources */,
+				ED6DAC0228C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */,
 				B146D4E421A5AEF200D8C2C6 /* MXRealmMediaScanMapper.m in Sources */,
 				B146D4E321A5AEF200D8C2C6 /* MXRealmMediaScan.m in Sources */,
 				ED5C754628B3E80300D24E85 /* MXLogger.m in Sources */,
@@ -6487,6 +6584,7 @@
 				B1136965230AC9D900E2B2FA /* MXIdentityService.m in Sources */,
 				66836AB727CFA17200515780 /* MXEventStreamService.swift in Sources */,
 				B11BD44922CB56790064D8B0 /* MXReplyEventParser.m in Sources */,
+				ED01915228C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */,
 				ED8F1D3B2885BB2D00F897E7 /* MXCryptoProtocols.swift in Sources */,
 				EC0B941127184E8A00B4D440 /* MXRoomSummaryMO.swift in Sources */,
 				EDBCF339281A8D3D00ED5044 /* MXSharedHistoryKeyService.m in Sources */,
@@ -6598,6 +6696,7 @@
 				B1F04B1A2812A4E000103EBE /* MXBeaconInfoSummaryProtocol.swift in Sources */,
 				021AFBA52179E91900742B2C /* MXEncryptedContentKey.m in Sources */,
 				32F634AC1FC5E3480054EF49 /* MXEventDecryptionResult.m in Sources */,
+				ED01915628C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */,
 				327137281A24D50A00DB6757 /* MXMyUser.m in Sources */,
 				ECDA763B27B6B74C000C48CF /* MXCapabilities.m in Sources */,
 				EC0B94242718E3EE00B4D440 /* MXCoreDataRoomListDataFetcher.swift in Sources */,
@@ -6640,6 +6739,7 @@
 				A780625027B2CE74005780C0 /* FileManager+AppGroupContainer.swift in Sources */,
 				9274AFE91EE580240009BEB6 /* MXCallKitAdapter.m in Sources */,
 				3274538C23FD918800438328 /* MXKeyVerificationByToDeviceRequest.m in Sources */,
+				ED6DAC1828C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */,
 				ECB6FA952683811800A941E4 /* MXiOSAudioOutputRouter.swift in Sources */,
 				EC131B0A2758D56600712964 /* MXThreadNotificationsCount.swift in Sources */,
 				EC1848C52685F64D00865E16 /* MXiOSAudioOutputRoute.swift in Sources */,
@@ -6653,6 +6753,7 @@
 				EC383BA5253DE6C9002FBBE6 /* MXSyncResponseStore.swift in Sources */,
 				32CEEF4523AD2A6C0039BA98 /* MXCrossSigningKey.m in Sources */,
 				327E9AF02289C61100A98BC1 /* MXAggregations.m in Sources */,
+				ED6DAC1B28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */,
 				B18B0E4A25FB783B00E32151 /* MXSpaceCreationParameters.swift in Sources */,
 				EDD578E52881C37C006739DD /* MXCrossSigningInfoSource.swift in Sources */,
 				EC2EACFF266625170038B61F /* MXRoomLastMessage.m in Sources */,
@@ -6679,6 +6780,7 @@
 				32A31BC920D401FC005916C7 /* MXRoomFilter.m in Sources */,
 				B190AC812833FA8900D6F0C4 /* MXBeaconInfoSummaryRealmStore.swift in Sources */,
 				EC11658F270F3ABF0089FA56 /* RLMRealm+MatrixSDK.m in Sources */,
+				ED6DAC0A28C784AE00ECDCB6 /* Dictionary.swift in Sources */,
 				32A151471DAF7C0C00400192 /* MXDeviceInfo.m in Sources */,
 				ECDA764E27BA963D000C48CF /* MXBooleanCapability.m in Sources */,
 				321CFDEB22525DEE004D31DF /* MXIncomingSASTransaction.m in Sources */,
@@ -6754,6 +6856,7 @@
 				B146D47B21A5958400D8C2C6 /* MXAntivirusScanStatusFormatter.m in Sources */,
 				EC8A53DE25B1BCC6004E0802 /* MXThirdpartyProtocolsResponse.m in Sources */,
 				32133022228BF7BC0070BA9B /* MXReactionCountChange.m in Sources */,
+				ED6DAC2228C7A51400ECDCB6 /* MXDateProvider.swift in Sources */,
 				32A151491DAF7C0C00400192 /* MXKey.m in Sources */,
 				324DD2AE246AEB7B00377005 /* MXSecretStoragePassphrase.m in Sources */,
 				ECB5D98C2552C9B4000AD89C /* MXStopwatch.swift in Sources */,
@@ -6777,6 +6880,7 @@
 				3291D4D51A68FFEB00C3BA41 /* MXFileRoomStore.m in Sources */,
 				329FB1801A0B665800A5E88E /* MXUser.m in Sources */,
 				324AAC73239913AD00380A66 /* MXKeyVerificationDone.m in Sources */,
+				ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */,
 				B11556EE230C45C600B2A2CF /* MXIdentityServerRestClient.swift in Sources */,
 				321CFDE722525A49004D31DF /* MXSASTransaction.m in Sources */,
 				32720D9D222EAA6F0086FFF5 /* MXDiscoveredClientConfig.m in Sources */,
@@ -6891,6 +6995,7 @@
 				EC51019D26C41981007D6D88 /* MXSyncResponseUnitTests.swift in Sources */,
 				EDB4209527DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */,
 				EC40385D28A16EDA0067D5B8 /* MXAes256KeyBackupTests.m in Sources */,
+				ED6DAC0728C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */,
 				3265CB3B1A151C3800E24B2F /* MXRoomStateTests.m in Sources */,
 				ED8F1D302885AB0300F897E7 /* MXTrustLevelSourceUnitTests.swift in Sources */,
 				B135066927EA100000BD3276 /* MXBeaconInfoUnitTests.swift in Sources */,
@@ -6901,6 +7006,7 @@
 				324BE45B1E3FA7A8008D99D4 /* MXMegolmExportEncryptionUnitTests.m in Sources */,
 				327E9ACF2284783E00A98BC1 /* MXEventAnnotationUnitTests.swift in Sources */,
 				EC40386128A1A3830067D5B8 /* MXBaseKeyBackupTests.m in Sources */,
+				ED6DAC0D28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */,
 				32EEA84A2603FDD60041425B /* MXResponseUnitTests.swift in Sources */,
 				32322A481E57264E005DD155 /* MXSelfSignedHomeserverTests.m in Sources */,
 				325653831A2E14ED00CC0423 /* MXStoreTests.m in Sources */,
@@ -6911,6 +7017,7 @@
 				32B477852638133C00EA5800 /* MXAggregatedEditsUnitTests.m in Sources */,
 				ECB6FA8E267CFF4300A941E4 /* MXCredentialsUnitTests.swift in Sources */,
 				ED7019F52886CA6C00FC31B9 /* MXKeyVerificationRequestV2UnitTests.swift in Sources */,
+				ED6DAC1128C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */,
 				3A108E6725826F52005EEBE9 /* MXKeyProviderUnitTests.m in Sources */,
 				3A858DE8275511A4006322C1 /* MXRoomAliasAvailabilityCheckerResultTests.swift in Sources */,
 				EC116593270FB6970089FA56 /* MXBackgroundTaskUnitTests.swift in Sources */,
@@ -6925,6 +7032,7 @@
 				EDF4678727E3331D00435913 /* EventsEnumeratorDataSourceStub.swift in Sources */,
 				EC746C59274E61EF002AD24C /* MXThreadingServiceTests.swift in Sources */,
 				32A31BC120D3F4C4005916C7 /* MXFilterTests.m in Sources */,
+				ED6DAC1528C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */,
 				32B477842638133C00EA5800 /* MXAggregatedReferenceUnitTests.m in Sources */,
 				32B0E3E423A384D40054FF1A /* MXAggregatedReferenceTests.m in Sources */,
 				32D5D16323E400A600E3E37C /* MXRoomSummaryTrustTests.m in Sources */,
@@ -6952,6 +7060,7 @@
 				EDE1B13B28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */,
 				EC746C56274E5197002AD24C /* MXThreadingServiceUnitTests.swift in Sources */,
 				ED8F1D252885A39800F897E7 /* MXCrossSigningInfoSourceUnitTests.swift in Sources */,
+				ED6DAC1E28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */,
 				32A27D1F19EC335300BAFADE /* MXRoomTests.m in Sources */,
 				32D8CAC219DEE6ED002AF8A0 /* MXRestClientNoAuthAPITests.m in Sources */,
 				32FCAB4D19E578860049C555 /* MXRestClientTests.m in Sources */,
@@ -7069,6 +7178,7 @@
 				B14EF1EF2397E90400758AF0 /* MXIdentityService.m in Sources */,
 				ECF29BE62641953C0053E6D6 /* MXAssertedIdentityModel.m in Sources */,
 				EC8A53C625B1BC77004E0802 /* MXTurnServerResponse.m in Sources */,
+				ED6DAC0328C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */,
 				B14EF1F02397E90400758AF0 /* MXReplyEventParser.m in Sources */,
 				B14EF1F12397E90400758AF0 /* MXFileStore.m in Sources */,
 				ED5C754728B3E80300D24E85 /* MXLogger.m in Sources */,
@@ -7086,6 +7196,7 @@
 				3A108A8125810C96005EEBE9 /* MXKeyData.m in Sources */,
 				66836AB827CFA17200515780 /* MXEventStreamService.swift in Sources */,
 				3A59A4A025A7A16F00DDA1FC /* MXOlmOutboundGroupSession.m in Sources */,
+				ED01915328C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */,
 				ED8F1D3C2885BB2D00F897E7 /* MXCryptoProtocols.swift in Sources */,
 				EC0B941227184E8A00B4D440 /* MXRoomSummaryMO.swift in Sources */,
 				EDBCF33A281A8D3D00ED5044 /* MXSharedHistoryKeyService.m in Sources */,
@@ -7197,6 +7308,7 @@
 				B1F04B1B2812A4E000103EBE /* MXBeaconInfoSummaryProtocol.swift in Sources */,
 				EC60EDDD265CFF0600B39A4E /* MXInvitedRoomSync.m in Sources */,
 				B14EF2292397E90400758AF0 /* MXRealmEventScanMapper.m in Sources */,
+				ED01915728C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */,
 				ECDA763C27B6B74C000C48CF /* MXCapabilities.m in Sources */,
 				EC0B94252718E3EF00B4D440 /* MXCoreDataRoomListDataFetcher.swift in Sources */,
 				B14EF22A2397E90400758AF0 /* MXReplyEventFormattedBodyParts.m in Sources */,
@@ -7239,6 +7351,7 @@
 				B14EF23B2397E90400758AF0 /* MXKeyBackupData.m in Sources */,
 				B14EF23C2397E90400758AF0 /* MXJSONModels.m in Sources */,
 				EC8A538E25B1BC77004E0802 /* MXCallSessionDescription.m in Sources */,
+				ED6DAC1928C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */,
 				EC131B0B2758D56600712964 /* MXThreadNotificationsCount.swift in Sources */,
 				B14EF23D2397E90400758AF0 /* MXCallManager.m in Sources */,
 				B14EF23E2397E90400758AF0 /* MXPeekingRoom.m in Sources */,
@@ -7252,6 +7365,7 @@
 				EC8A53A625B1BC77004E0802 /* MXCallInviteEventContent.m in Sources */,
 				ECD2899226EB3B3400F268CF /* MXRoomListDataFetcher.swift in Sources */,
 				B18B0E5025FB783F00E32151 /* MXSpaceService.swift in Sources */,
+				ED6DAC1C28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */,
 				B14EF2412397E90400758AF0 /* MXRoomFilter.m in Sources */,
 				EDD578E62881C37C006739DD /* MXCrossSigningInfoSource.swift in Sources */,
 				EC8A53D925B1BCC6004E0802 /* MXThirdPartyProtocolInstance.m in Sources */,
@@ -7278,6 +7392,7 @@
 				B135066F27EA44C800BD3276 /* MXLocationServiceError.swift in Sources */,
 				B190AC822833FA8900D6F0C4 /* MXBeaconInfoSummaryRealmStore.swift in Sources */,
 				B14EF24B2397E90400758AF0 /* MXServiceTermsRestClient.m in Sources */,
+				ED6DAC0B28C784AE00ECDCB6 /* Dictionary.swift in Sources */,
 				EC1165C527107E330089FA56 /* MXRoomListDataFilterOptions.swift in Sources */,
 				3A858DE227528EEB006322C1 /* MXHomeserverCapabilitiesService.swift in Sources */,
 				ECDA764F27BA963D000C48CF /* MXBooleanCapability.m in Sources */,
@@ -7353,6 +7468,7 @@
 				B14EF26B2397E90400758AF0 /* MXMegolmBackupCreationInfo.m in Sources */,
 				B14EF26C2397E90400758AF0 /* MXRoom.m in Sources */,
 				B14EF26D2397E90400758AF0 /* NSData+MatrixSDK.m in Sources */,
+				ED6DAC2128C7A51400ECDCB6 /* MXDateProvider.swift in Sources */,
 				EC383BB22540688E002FBBE6 /* MXBackgroundStore.swift in Sources */,
 				B14EF26E2397E90400758AF0 /* MXFileRoomStore.m in Sources */,
 				EC60ED9D265CFE1700B39A4E /* MXRoomSyncState.m in Sources */,
@@ -7376,6 +7492,7 @@
 				B18B0E4B25FB783B00E32151 /* MXSpaceCreationParameters.swift in Sources */,
 				B14EF2752397E90400758AF0 /* MXResponse.swift in Sources */,
 				B14EF2772397E90400758AF0 /* MXDecryptionResult.m in Sources */,
+				ED6DABFD28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */,
 				B14EF2782397E90400758AF0 /* MXTransactionCancelCode.m in Sources */,
 				B14EF2792397E90400758AF0 /* MXEventListener.m in Sources */,
 				B1710B202613D01400A9B429 /* MXSpaceChildrenRequestParameters.swift in Sources */,
@@ -7490,6 +7607,7 @@
 				EC51019E26C41981007D6D88 /* MXSyncResponseUnitTests.swift in Sources */,
 				EDB4209627DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */,
 				EC40385E28A16EDA0067D5B8 /* MXAes256KeyBackupTests.m in Sources */,
+				ED6DAC0828C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */,
 				32B477AA2638186000EA5800 /* MXHTTPAdditionalHeadersUnitTests.m in Sources */,
 				B135066A27EA100100BD3276 /* MXBeaconInfoUnitTests.swift in Sources */,
 				EC0B944727206D0B00B4D440 /* MXCoreDataRoomListDataManagerUnitTests.swift in Sources */,
@@ -7500,6 +7618,7 @@
 				B1E09A3E2397FD820057C069 /* MXToolsUnitTests.m in Sources */,
 				32B477912638133D00EA5800 /* MXAggregatedEditsUnitTests.m in Sources */,
 				EC40386228A1A3830067D5B8 /* MXBaseKeyBackupTests.m in Sources */,
+				ED6DAC0E28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */,
 				B1E09A1E2397FCE90057C069 /* MXCryptoShareTests.m in Sources */,
 				B1E09A422397FD820057C069 /* MXCryptoTests.m in Sources */,
 				B1E09A382397FD7D0057C069 /* MXUserTests.m in Sources */,
@@ -7510,6 +7629,7 @@
 				32AF9293241112850008A0FD /* MXCryptoSecretShareTests.m in Sources */,
 				B1E09A462397FD990057C069 /* MXMediaScanStoreUnitTests.m in Sources */,
 				ED7019F62886CA6C00FC31B9 /* MXKeyVerificationRequestV2UnitTests.swift in Sources */,
+				ED6DAC1228C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */,
 				ECB6FA8F267CFF4300A941E4 /* MXCredentialsUnitTests.swift in Sources */,
 				3A858DE9275511A4006322C1 /* MXRoomAliasAvailabilityCheckerResultTests.swift in Sources */,
 				ED7019DF2886C25600FC31B9 /* MXDeviceInfoUnitTests.swift in Sources */,
@@ -7524,6 +7644,7 @@
 				B1B4431A283D00CA00BB26F4 /* MXMegolmDecryptionUnitTests.swift in Sources */,
 				EDF4678827E3331D00435913 /* EventsEnumeratorDataSourceStub.swift in Sources */,
 				EC746C5A274E61EF002AD24C /* MXThreadingServiceTests.swift in Sources */,
+				ED6DAC1628C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */,
 				B1E09A222397FCE90057C069 /* MXRoomSummaryTests.m in Sources */,
 				B1E09A3A2397FD820057C069 /* MXStoreTests.m in Sources */,
 				B1E09A342397FD750057C069 /* MXRoomStateDynamicTests.m in Sources */,
@@ -7551,6 +7672,7 @@
 				EDE1B13C28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */,
 				A816248525F60D0300A46F05 /* MXDeviceListOperationsPoolUnitTests.swift in Sources */,
 				EC746C57274E5197002AD24C /* MXThreadingServiceUnitTests.swift in Sources */,
+				ED6DAC1F28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */,
 				B1E09A312397FD750057C069 /* MXSessionTests.m in Sources */,
 				32B477902638133D00EA5800 /* MXAggregatedReferenceUnitTests.m in Sources */,
 				EC116598270FCA8B0089FA56 /* MXBackgroundTaskUnitTests.swift in Sources */,

programming_language: Scala
filename: MXBackgroundSyncService.swift
change_type: ModificationType.MODIFY
diff: @@ -564,6 +564,12 @@ public enum MXBackgroundSyncServiceError: Error {
     }
     
     private func handleToDeviceEvent(_ event: MXEvent) {
+        //   only handle supported events
+        guard MXTools.isSupportedToDeviceEvent(event) else {
+            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ignore unsupported event")
+            return
+        }
+        
         if event.isEncrypted {
             do {
                 try decryptEvent(event)
@@ -573,61 +579,35 @@ public enum MXBackgroundSyncServiceError: Error {
             }
         }
         
-        guard let content = event.content else {
-            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ERROR: incomplete event content: \(String(describing: event.jsonDictionary()))")
+        guard let userId = credentials.userId else {
+            MXLog.error("[MXBackgroundSyncService] handleToDeviceEvent: Cannot get userId")
             return
         }
         
-        guard let roomId = content["room_id"] as? String,
-            let sessionId = content["session_id"] as? String,
-            let sessionKey = content["session_key"] as? String,
-            var senderKey = event.senderKey else {
-            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ERROR: incomplete event: \(String(describing: event.jsonDictionary()))")
+        let factory = MXRoomKeyInfoFactory(myUserId: userId, store: cryptoStore)
+        guard let key = factory.roomKey(for: event) else {
+            MXLog.error("[MXBackgroundSyncService] handleToDeviceEvent: Cannot create megolm key from event")
             return
         }
         
-        var forwardingKeyChain: [String] = []
-        var exportFormat: Bool = false
-        var keysClaimed: [String: String] = [:]
-        
-        switch event.eventType {
-        case .roomKey:
-            keysClaimed = event.keysClaimed as! [String: String]
-        case .roomForwardedKey:
-            exportFormat = true
-            
-            if let array = content["forwarding_curve25519_key_chain"] as? [String] {
-                forwardingKeyChain = array
-            }
-            forwardingKeyChain.append(senderKey)
-            
-            if let senderKeyInContent = content["sender_key"] as? String {
-                senderKey = senderKeyInContent
-            } else {
-                return
-            }
-            
-            guard let ed25519Key = event.content["sender_claimed_ed25519_key"] as? String else {
-                return
-            }
-            
-            keysClaimed = [
-                "ed25519": ed25519Key
-            ]
-        default:
-            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ERROR: Not supported type: \(event.eventType)")
-            return
+        switch key.type {
+        case .safe:
+            olmDevice.addInboundGroupSession(
+                key.info.sessionId,
+                sessionKey: key.info.sessionKey,
+                roomId: key.info.roomId,
+                senderKey: key.info.senderKey,
+                forwardingCurve25519KeyChain: key.info.forwardingKeyChain,
+                keysClaimed: key.info.keysClaimed,
+                exportFormat: key.info.exportFormat,
+                sharedHistory: key.info.sharedHistory,
+                untrusted: key.type != .safe
+            )
+        case .unsafe:
+            MXLog.warning("[MXBackgroundSyncService] handleToDeviceEvent: Ignoring unsafe keys")
+        case .unrequested:
+            MXLog.warning("[MXBackgroundSyncService] handleToDeviceEvent: Ignoring unrequested keys")
         }
-        
-        let sharedHistory = (content[kMXSharedHistoryKeyName] as? Bool) ?? false
-        olmDevice.addInboundGroupSession(sessionId,
-                                         sessionKey: sessionKey,
-                                         roomId: roomId,
-                                         senderKey: senderKey,
-                                         forwardingCurve25519KeyChain: forwardingKeyChain,
-                                         keysClaimed: keysClaimed,
-                                         exportFormat: exportFormat,
-                                         sharedHistory: sharedHistory)
     }
     
     private func updateBackgroundServiceStoresIfNeeded() {

programming_language: Swift
filename: MXEvent.swift
change_type: ModificationType.MODIFY
diff: @@ -73,6 +73,9 @@ public enum MXEventType: Equatable, Hashable {
     case keyVerificationMac
     case keyVerificationCancel
     case keyVerificationDone
+    case secretRequest
+    case secretSend
+    case secretStorageDefaultKey
     case taggedEvents
     case spaceChild
     case spaceOrder
@@ -132,6 +135,9 @@ public enum MXEventType: Equatable, Hashable {
         case .keyVerificationMac: return kMXEventTypeStringKeyVerificationMac
         case .keyVerificationCancel: return kMXEventTypeStringKeyVerificationCancel
         case .keyVerificationDone: return kMXEventTypeStringKeyVerificationDone
+        case .secretRequest: return kMXEventTypeStringSecretRequest
+        case .secretSend: return kMXEventTypeStringSecretSend
+        case .secretStorageDefaultKey: return kMXEventTypeStringSecretStorageDefaultKey
         case .taggedEvents: return kMXEventTypeStringTaggedEvents
         case .spaceChild: return kMXEventTypeStringSpaceChild
         case .spaceOrder: return kMXEventTypeStringSpaceOrderMSC3230
@@ -151,7 +157,7 @@ public enum MXEventType: Equatable, Hashable {
     }
     
     public init(identifier: String) {
-        let events: [MXEventType] = [.roomName, .roomTopic, .roomAvatar, .roomMember, .roomCreate, .roomJoinRules, .roomPowerLevels, .roomAliases, .roomCanonicalAlias, .roomEncrypted, .roomEncryption, .roomGuestAccess, .roomHistoryVisibility, .roomKey, .roomForwardedKey, .roomKeyRequest, .roomMessage, .roomMessageFeedback, .roomRedaction, .roomThirdPartyInvite, .roomTag, .presence, .typing, .callInvite, .callCandidates, .callAnswer, .callSelectAnswer, .callHangup, .callReject, .callNegotiate, .callReplaces, .callRejectReplacement, .callAssertedIdentity, .callAssertedIdentityUnstable, .reaction, .receipt, .roomTombStone, .keyVerificationStart, .keyVerificationAccept, .keyVerificationKey, .keyVerificationMac, .keyVerificationCancel, .keyVerificationDone, .taggedEvents, .spaceChild, .spaceOrder, .pollStart, .pollResponse, .pollEnd, .beaconInfo, .beacon]
+        let events: [MXEventType] = [.roomName, .roomTopic, .roomAvatar, .roomMember, .roomCreate, .roomJoinRules, .roomPowerLevels, .roomAliases, .roomCanonicalAlias, .roomEncrypted, .roomEncryption, .roomGuestAccess, .roomHistoryVisibility, .roomKey, .roomForwardedKey, .roomKeyRequest, .roomMessage, .roomMessageFeedback, .roomRedaction, .roomThirdPartyInvite, .roomTag, .presence, .typing, .callInvite, .callCandidates, .callAnswer, .callSelectAnswer, .callHangup, .callReject, .callNegotiate, .callReplaces, .callRejectReplacement, .callAssertedIdentity, .callAssertedIdentityUnstable, .reaction, .receipt, .roomTombStone, .keyVerificationStart, .keyVerificationAccept, .keyVerificationKey, .keyVerificationMac, .keyVerificationCancel, .keyVerificationDone, .secretRequest, .secretSend, .secretStorageDefaultKey, .taggedEvents, .spaceChild, .spaceOrder, .pollStart, .pollResponse, .pollEnd, .beaconInfo, .beacon]
         
         if let type = events.first(where: { $0.identifier == identifier }) {
             self = type

programming_language: Swift
filename: MXDecrypting.h
change_type: ModificationType.MODIFY
diff: @@ -23,7 +23,7 @@
 #import "MXEventDecryptionResult.h"
 #import "MXIncomingRoomKeyRequest.h"
 
-@class MXCrypto, MXOlmInboundGroupSession;
+@class MXCrypto, MXOlmInboundGroupSession, MXRoomKeyResult;
 
 
 @protocol MXDecrypting <NSObject>
@@ -56,12 +56,19 @@
 - (MXEventDecryptionResult *)decryptEvent:(MXEvent*)event inTimeline:(NSString*)timeline;
 
 /**
- * Handle a key event.
- *
- * @param event the key event.
+ Handle a key event.
+ 
+ @param event the key event.
  */
 - (void)onRoomKeyEvent:(MXEvent*)event;
 
+/**
+ Handle new room key
+ 
+ @param key the domain object with key details and safety
+ */
+- (void)onRoomKey:(MXRoomKeyResult*)key;
+
 /**
  Notification that a room key has been imported.
 

programming_language: Objective-C
filename: MXMegolmDecryption.m
change_type: ModificationType.MODIFY
diff: @@ -26,6 +26,7 @@
 #import "MXTools.h"
 #import "MatrixSDKSwiftHeader.h"
 #import "MXSharedHistoryKeyService.h"
+#import "MXForwardedRoomKeyEventContent.h"
 
 @interface MXMegolmDecryption ()
 {
@@ -41,6 +42,10 @@
         NSMutableDictionary<NSString* /* timelineId */,
             NSMutableDictionary<NSString* /* eventId */, MXEvent*>*>*> *pendingEvents;
 }
+
+// Factory to create room key info
+@property (nonatomic, strong) MXRoomKeyInfoFactory *roomKeyInfoFactory;
+
 @end
 
 @implementation MXMegolmDecryption
@@ -59,6 +64,7 @@
     {
         crypto = theCrypto;
         olmDevice = theCrypto.olmDevice;
+        _roomKeyInfoFactory = [[MXRoomKeyInfoFactory alloc] initWithMyUserId:crypto.mxSession.credentials.userId store:crypto.store];
         pendingEvents = [NSMutableDictionary dictionary];
     }
     return self;
@@ -191,98 +197,59 @@
 
 - (void)onRoomKeyEvent:(MXEvent *)event
 {
-    NSDictionary *content = event.content;
-    NSString *roomId, *sessionId, *sessionKey;
-
-    MXJSONModelSetString(roomId, content[@"room_id"]);
-    MXJSONModelSetString(sessionId, content[@"session_id"]);
-    MXJSONModelSetString(sessionKey, content[@"session_key"]);
-
-    if (!roomId || !sessionId || !sessionKey)
+    MXRoomKeyResult *key = [self.roomKeyInfoFactory roomKeyFor:event];
+    if (!key)
     {
-        MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: Key event is missing fields");
+        MXLogError(@"[MXMegolmDecryption] onRoomKeyEvent: Cannot create megolm key from event");
         return;
     }
-
-    NSString *senderKey = event.senderKey;
-    if (!senderKey)
-    {
-        MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: Key event has no sender key (not encrypted?)");
-        return;
-    }
-
-    NSArray<NSString*> *forwardingKeyChain;
-    BOOL exportFormat = NO;
-    NSDictionary *keysClaimed;
-    BOOL sharedHistory = NO;
-    if (content[kMXSharedHistoryKeyName] != nil)
-    {
-        MXJSONModelSetBoolean(sharedHistory, content[kMXSharedHistoryKeyName]);
-    }
-
-    if (event.eventType == MXEventTypeRoomForwardedKey)
-    {
-        exportFormat = YES;
-        MXJSONModelSetArray(forwardingKeyChain, content[@"forwarding_curve25519_key_chain"]);
-        if (!forwardingKeyChain)
-        {
-            forwardingKeyChain = @[];
-        }
-
-        // copy content before we modify it
-        NSMutableArray *forwardingKeyChain2 = [NSMutableArray arrayWithArray:forwardingKeyChain];
-        [forwardingKeyChain2 addObject:senderKey];
-        forwardingKeyChain = forwardingKeyChain2;
-
-        MXJSONModelSetString(senderKey, content[@"sender_key"]);
-        if (!senderKey)
-        {
-            MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: forwarded_room_key event is missing sender_key field");
-            return;
-        }
-
-        NSString *ed25519Key;
-        MXJSONModelSetString(ed25519Key, content[@"sender_claimed_ed25519_key"]);
-        if (!ed25519Key)
-        {
-            MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: forwarded_room_key_event is missing sender_claimed_ed25519_key field");
-            return;
-        }
-
-        keysClaimed = @{
-                        @"ed25519": ed25519Key
-                        };
-    }
-    else
-    {
-        keysClaimed = event.keysClaimed;
+    
+    switch (key.type) {
+        case MXRoomKeyTypeSafe:
+            MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: Adding key for megolm session %@|%@ from %@ event", key.info.senderKey, key.info.sessionId, event.type);
+            [self onRoomKey:key];
+            break;
+        case MXRoomKeyTypeUnsafe:
+            MXLogWarning(@"[MXMegolmDecryption] onRoomKeyEvent: Ignoring unsafe key");
+            break;
+        case MXRoomKeyTypeUnrequested:
+            [crypto handleUnrequestedRoomKeyInfo:key.info senderId:event.sender senderKey:event.senderKey];
+            break;
+        default:
+            MXLogFailureDetails(@"[MXMegolmDecryption] onRoomKeyEvent: Unknown key type", @{
+                @"key_type": @(key.type)
+            });
+            break;
     }
+}
 
-    MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: Adding key for megolm session %@|%@ from %@ event", senderKey, sessionId, event.type);
-
-    [olmDevice addInboundGroupSession:sessionId
-                           sessionKey:sessionKey
-                               roomId:roomId
-                            senderKey:senderKey
-         forwardingCurve25519KeyChain:forwardingKeyChain
-                          keysClaimed:keysClaimed
-                         exportFormat:exportFormat
-                        sharedHistory:sharedHistory];
+- (void)onRoomKey:(MXRoomKeyResult *)key
+{
+    MXRoomKeyInfo *keyInfo = key.info;
+    [olmDevice addInboundGroupSession:keyInfo.sessionId
+                           sessionKey:keyInfo.sessionKey
+                               roomId:keyInfo.roomId
+                            senderKey:keyInfo.senderKey
+         forwardingCurve25519KeyChain:keyInfo.forwardingKeyChain
+                          keysClaimed:keyInfo.keysClaimed
+                         exportFormat:keyInfo.exportFormat
+                        sharedHistory:keyInfo.sharedHistory
+                            untrusted:key.type != MXRoomKeyTypeSafe];
 
     [crypto.backup maybeSendKeyBackup];
 
     MXWeakify(self);
-    [self retryDecryption:senderKey sessionId:content[@"session_id"] complete:^(BOOL allDecrypted) {
+    [self retryDecryption:keyInfo.senderKey sessionId:keyInfo.sessionId complete:^(BOOL allDecrypted) {
         MXStrongifyAndReturnIfNil(self);
 
         if (allDecrypted)
         {
             // cancel any outstanding room key requests for this session
             [self->crypto cancelRoomKeyRequest:@{
-                                                 @"algorithm": content[@"algorithm"],
-                                                 @"room_id": content[@"room_id"],
-                                                 @"session_id": content[@"session_id"],
-                                                 @"sender_key": senderKey
+                                                 @"algorithm": keyInfo.algorithm,
+                                                 @"room_id": keyInfo.roomId,
+                                                 @"session_id": keyInfo.sessionId,
+                                                 @"sender_key": keyInfo.senderKey
                                                  }];
         }
     }];

programming_language: Objective-C
filename: MXMegolmEncryption.m
change_type: ModificationType.MODIFY
diff: @@ -340,6 +340,7 @@
                                                }
                                 exportFormat:NO
                                sharedHistory:sharedHistory
+                                   untrusted:NO
      ];
 
     [crypto.backup maybeSendKeyBackup];

programming_language: Objective-C
filename: MXOlmDecryption.m
change_type: ModificationType.MODIFY
diff: @@ -223,6 +223,11 @@
     // No impact for olm
 }
 
+- (void)onRoomKeyInfo:(MXRoomKeyInfo *)keyInfo
+{
+    // No impact for olm
+}
+
 - (void)didImportRoomKey:(MXOlmInboundGroupSession *)session
 {
     // No impact for olm

programming_language: Objective-C
filename: MXKeyBackupVersionTrust.h
change_type: ModificationType.MODIFY
diff: @@ -39,11 +39,6 @@ NS_ASSUME_NONNULL_BEGIN
  */
 @property (nonatomic) NSArray<MXKeyBackupVersionTrustSignature*> *signatures;
 
-/**
- Flag indicating the backup trusted locally.
- */
-@property (nonatomic, getter=isTrustedLocally) BOOL trustedLocally;
-
 @end
 
 

programming_language: Objective-C
filename: MXKeyBackupVersionTrust.m
change_type: ModificationType.MODIFY
diff: @@ -25,7 +25,6 @@
     {
         _usable = NO;
         _signatures = [NSArray new];
-        _trustedLocally = NO;
     }
     return self;
 }

programming_language: Objective-C
filename: MXKeyBackup.m
change_type: ModificationType.MODIFY
diff: @@ -1123,17 +1123,6 @@ static Class DefaultAlgorithmClass;
         return keyBackupVersionTrust;
     }
 
-    NSData *privateKey = self.privateKeyFromCryptoStore;
-    if (privateKey)
-    {
-        id<MXKeyBackupAlgorithm> algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey];
-        if ([algorithm keyMatches:privateKey error:nil])
-        {
-            MXLogDebug(@"[MXKeyBackup] trustForKeyBackupVersionFromCryptoQueue: Backup is trusted locally");
-            keyBackupVersionTrust.trustedLocally = YES;
-        }
-    }
-
     NSDictionary *mySigs = authData.signatures[myUserId];
     NSMutableArray<MXKeyBackupVersionTrustSignature*> *signatures = [NSMutableArray array];
     for (NSString *keyId in mySigs)
@@ -1196,7 +1185,6 @@ static Class DefaultAlgorithmClass;
             keyBackupVersionTrust.usable = YES;
         }
     }
-    keyBackupVersionTrust.usable = keyBackupVersionTrust.usable || keyBackupVersionTrust.isTrustedLocally;
 
     return keyBackupVersionTrust;
 }

programming_language: Objective-C
filename: MXForwardedRoomKeyEventContent.h
change_type: ModificationType.ADD
diff: @@ -0,0 +1,74 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <MatrixSDK/MatrixSDK.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MXForwardedRoomKeyEventContent : MXJSONModel
+
+/**
+ The encryption algorithm the key in this event is to be used with
+ */
+@property (nonatomic) NSString *algorithm;
+
+/**
+ The room where the key is used
+ */
+@property (nonatomic) NSString *roomId;
+
+/**
+ The Curve25519 key of the device which initiated the session originally
+ */
+@property (nonatomic) NSString *senderKey;
+
+/**
+ The ID of the session that the key is for
+ */
+@property (nonatomic) NSString *sessionId;
+
+/**
+ The key to be exchanged
+ */
+@property (nonatomic) NSString *sessionKey;
+
+/**
+ Chain of Curve25519 keys
+ 
+ It starts out empty, but each time the key is forwarded to another device, the previous sender
+ in the chain is added to the end of the list.
+ 
+ For example, if the key is forwarded from A to B to C, this field is empty between A and B,
+ and contains A's Curve25519 key between B and C
+ */
+@property (nonatomic) NSArray<NSString*> *forwardingCurve25519KeyChain;
+
+/**
+ The Ed25519 key of the device which initiated the session originally
+ 
+ It is 'claimed' because the receiving device has no way to tell that the original room_key actually
+ came from a device which owns the private part of this key unless they have done device verification.
+ */
+@property (nonatomic) NSString *senderClaimedEd25519Key;
+
+/**
+ MSC3061 Identifies keys that were sent when the room's visibility setting was set to `world_readable` or `shared`
+ */
+@property (nonatomic) BOOL sharedHistory;
+
+@end
+
+NS_ASSUME_NONNULL_END

programming_language: Objective-C
filename: MXForwardedRoomKeyEventContent.m
change_type: ModificationType.ADD
diff: @@ -0,0 +1,66 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "MXForwardedRoomKeyEventContent.h"
+
+static NSString* const kJSONKeyAlgorithm = @"algorithm";
+static NSString* const kJSONKeyRoomId = @"room_id";
+static NSString* const kJSONKeySenderKey = @"sender_key";
+static NSString* const kJSONKeySessionId = @"session_id";
+static NSString* const kJSONKeySessionKey = @"session_key";
+static NSString* const kJSONKeyForwardingCurve25519KeyChain = @"forwarding_curve25519_key_chain";
+static NSString* const kJSONKeySenderClaimedEd25519Key = @"sender_claimed_ed25519_key";
+
+@implementation MXForwardedRoomKeyEventContent
+
+#pragma mark - MXJSONModel
+
++ (id)modelFromJSON:(NSDictionary *)JSONDictionary
+{
+    MXForwardedRoomKeyEventContent *result = [[MXForwardedRoomKeyEventContent alloc] init];
+    MXJSONModelSetString(result.algorithm, JSONDictionary[kJSONKeyAlgorithm]);
+    MXJSONModelSetString(result.roomId, JSONDictionary[kJSONKeyRoomId]);
+    MXJSONModelSetString(result.sessionId, JSONDictionary[kJSONKeySessionId]);
+    MXJSONModelSetString(result.sessionKey, JSONDictionary[kJSONKeySessionKey]);
+    MXJSONModelSetString(result.senderKey, JSONDictionary[kJSONKeySenderKey]);
+    MXJSONModelSetString(result.senderClaimedEd25519Key, JSONDictionary[kJSONKeySenderClaimedEd25519Key]);
+    if (!result.algorithm || !result.roomId || !result.sessionId || !result.sessionKey || !result.senderKey || !result.senderClaimedEd25519Key)
+    {
+        MXLogError(@"[MXRoomKeyEventContent] modelFromJSON: Key event is missing fields");
+        return nil;
+    }
+    
+    MXJSONModelSetArray(result.forwardingCurve25519KeyChain, JSONDictionary[kJSONKeyForwardingCurve25519KeyChain] ?: @[]);
+    MXJSONModelSetBoolean(result.sharedHistory, JSONDictionary[kMXSharedHistoryKeyName]);
+
+    return result;
+}
+
+- (NSDictionary *)JSONDictionary
+{
+    NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary];
+    JSONDictionary[kJSONKeyAlgorithm] = _algorithm;
+    JSONDictionary[kJSONKeyRoomId] = _roomId;
+    JSONDictionary[kJSONKeySenderKey] = _senderKey;
+    JSONDictionary[kJSONKeySessionId] = _sessionId;
+    JSONDictionary[kJSONKeySessionKey] = _sessionKey;
+    JSONDictionary[kJSONKeyForwardingCurve25519KeyChain] = _forwardingCurve25519KeyChain;
+    JSONDictionary[kJSONKeySenderClaimedEd25519Key] = _senderClaimedEd25519Key;
+    JSONDictionary[kMXSharedHistoryKeyName] = @(_sharedHistory);
+    return JSONDictionary;
+}
+
+@end

programming_language: Objective-C
filename: MXRoomKeyEventContent.h
change_type: ModificationType.ADD
diff: @@ -0,0 +1,50 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <MatrixSDK/MatrixSDK.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface MXRoomKeyEventContent : MXJSONModel
+
+/**
+ The encryption algorithm the key in this event is to be used with
+ */
+@property (nonatomic) NSString *algorithm;
+
+/**
+ The room where the key is used
+ */
+@property (nonatomic) NSString *roomId;
+
+/**
+ The ID of the session that the key is for
+ */
+@property (nonatomic) NSString *sessionId;
+
+/**
+ The key to be exchanged
+ */
+@property (nonatomic) NSString *sessionKey;
+
+/**
+ MSC3061 Identifies keys that were sent when the room's visibility setting was set to `world_readable` or `shared`
+ */
+@property (nonatomic) BOOL sharedHistory;
+
+@end
+
+NS_ASSUME_NONNULL_END

programming_language: Objective-C
filename: MXRoomKeyEventContent.m
change_type: ModificationType.ADD
diff: @@ -0,0 +1,58 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import "MXRoomKeyEventContent.h"
+
+static NSString* const kJSONKeyAlgorithm = @"algorithm";
+static NSString* const kJSONKeyRoomId = @"room_id";
+static NSString* const kJSONKeySessionId = @"session_id";
+static NSString* const kJSONKeySessionKey = @"session_key";
+
+@implementation MXRoomKeyEventContent
+
+#pragma mark - MXJSONModel
+
++ (id)modelFromJSON:(NSDictionary *)JSONDictionary
+{
+    MXRoomKeyEventContent *result = [MXRoomKeyEventContent new];
+    MXJSONModelSetString(result.algorithm, JSONDictionary[kJSONKeyAlgorithm]);
+    MXJSONModelSetString(result.roomId, JSONDictionary[kJSONKeyRoomId]);
+    MXJSONModelSetString(result.sessionId, JSONDictionary[kJSONKeySessionId]);
+    MXJSONModelSetString(result.sessionKey, JSONDictionary[kJSONKeySessionKey]);
+    if (!result.algorithm || !result.roomId || !result.sessionId || !result.sessionKey)
+    {
+        MXLogError(@"[MXRoomKeyEventContent] modelFromJSON: Key event is missing fields");
+        return nil;
+    }
+
+    MXJSONModelSetBoolean(result.sharedHistory, JSONDictionary[kMXSharedHistoryKeyName]);
+    
+
+    return result;
+}
+
+- (NSDictionary *)JSONDictionary
+{
+    NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary];
+    JSONDictionary[kJSONKeyAlgorithm] = _algorithm;
+    JSONDictionary[kJSONKeyRoomId] = _roomId;
+    JSONDictionary[kJSONKeySessionId] = _sessionId;
+    JSONDictionary[kJSONKeySessionKey] = _sessionKey;
+    JSONDictionary[kMXSharedHistoryKeyName] = @(_sharedHistory);
+    return JSONDictionary;
+}
+
+@end

programming_language: Objective-C
filename: MXUnrequestedForwardedRoomKeyManager.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,153 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+@objc public protocol MXUnrequestedForwardedRoomKeyManagerDelegate: AnyObject {
+    func downloadDeviceKeys(userId: String, completion: @escaping (MXUsersDevicesMap<MXDeviceInfo>) -> Void)
+    func acceptRoomKey(keyInfo: MXRoomKeyInfo)
+}
+
+@objcMembers
+public class MXUnrequestedForwardedRoomKeyManager: NSObject {
+    private typealias RoomId = String
+    private typealias UserId = String
+    
+    static let MaximumTimeInterval: TimeInterval = 10 * 60
+    
+    struct PendingKey {
+        let info: MXRoomKeyInfo
+        let date: Date
+    }
+    
+    struct RoomInvite {
+        let roomId: String
+        let senderId: String
+        let date: Date
+    }
+    
+    public weak var delegate: MXUnrequestedForwardedRoomKeyManagerDelegate?
+    
+    private let dateProvider: MXDateProviding
+    private var pendingKeys = [RoomId: [UserId: [PendingKey]]]()
+    private var roomInvites = [RoomInvite]()
+    
+    override public init() {
+        self.dateProvider = MXDateProvider()
+    }
+    
+    init(dateProvider: MXDateProviding) {
+        self.dateProvider = dateProvider
+    }
+    
+    public func close() {
+        pendingKeys = [:]
+        roomInvites = []
+    }
+    
+    public func addPendingKey(keyInfo: MXRoomKeyInfo, senderId: String, senderKey: String) {
+        guard let delegate = delegate else {
+            MXLog.error("[MXUnrequestedForwardedRoomKeyManager] addPendingKey: Delegate is not set")
+            return
+        }
+        
+        // If just invited by the user we may not yet have their keys locally
+        delegate.downloadDeviceKeys(userId: senderId) { [weak self] keys in
+            guard let self = self else { return }
+            
+            guard let userId = self.matchingUserId(in: keys, userId: senderId, identityKey: senderKey) else {
+                MXLog.error("[MXUnrequestedForwardedRoomKeyManager] addPendingKey: senderId does not match the claimed senderKey")
+                return
+            }
+            self.addPendingKey(keyInfo: keyInfo, confirmedSenderId: userId)
+            self.processUnrequestedKeys()
+        }
+    }
+    
+    public func onRoomInvite(roomId: String, senderId: String) {
+        roomInvites.append(
+            .init(
+                roomId: roomId,
+                senderId: senderId,
+                date: dateProvider.currentDate()
+            )
+        )
+    }
+    
+    public func processUnrequestedKeys() {
+        guard let delegate = delegate else {
+            MXLog.error("[MXUnrequestedForwardedRoomKeyManager] processUnrequestedKeys: Delegate is not set")
+            return
+        }
+        
+        let now = dateProvider.currentDate()
+        
+        roomInvites.removeAll {
+            !$0.date.isWithin(timeInterval: Self.MaximumTimeInterval, of: now)
+        }
+        
+        for invite in roomInvites {
+            guard let roomKeys = pendingKeys[invite.roomId] else {
+                continue
+            }
+            
+            for (senderId, keys) in roomKeys {
+                if invite.senderId == senderId {
+                    for key in keys {
+                        guard key.date.isWithin(timeInterval: Self.MaximumTimeInterval, of: invite.date) else {
+                            continue
+                        }
+                        delegate.acceptRoomKey(keyInfo: key.info)
+                    }
+                }
+            }
+            
+            pendingKeys[invite.roomId] = nil
+        }
+    }
+    
+    // MARK: - Private
+    
+    private func matchingUserId(in deviceKeys: MXUsersDevicesMap<MXDeviceInfo>, userId: String, identityKey: String) -> String? {
+        return deviceKeys
+            .objects(forUser: userId)?
+            .first { $0.identityKey == identityKey }
+            .flatMap { $0.userId }
+    }
+    
+    private func addPendingKey(keyInfo: MXRoomKeyInfo, confirmedSenderId: String) {
+        if pendingKeys[keyInfo.roomId] == nil {
+            pendingKeys[keyInfo.roomId] = [:]
+        }
+        
+        if pendingKeys[keyInfo.roomId]![confirmedSenderId] == nil {
+            pendingKeys[keyInfo.roomId]![confirmedSenderId] = []
+        }
+        
+        pendingKeys[keyInfo.roomId]![confirmedSenderId]!.append(
+            .init(
+                info: keyInfo,
+                date: dateProvider.currentDate()
+            )
+        )
+    }
+}
+
+private extension Date {
+    func isWithin(timeInterval: TimeInterval, of date: Date) -> Bool {
+        return abs(self.timeIntervalSince(date)) < timeInterval
+    }
+}

programming_language: Swift
filename: MXSecretShareManager.m
change_type: ModificationType.MODIFY
diff: @@ -402,6 +402,12 @@ static NSArray<MXEventTypeString> *kMXSecretShareEventTypes;
 
 - (void)handleSecretSendEvent:(MXEvent*)event
 {
+    if (![self canAcceptSecretSendEvent:event])
+    {
+        MXLogDebug(@"[MXSecretShareManager] handleSecretSendEvent: Rejecting unacceptable secret");
+        return;
+    }
+    
     MXSecretShareSend *shareSend;
     MXJSONModelSetMXJSONModel(shareSend, MXSecretShareSend, event.content);
     if (!shareSend)
@@ -428,4 +434,29 @@ static NSArray<MXEventTypeString> *kMXSecretShareEventTypes;
     }
 }
 
+- (BOOL)canAcceptSecretSendEvent:(MXEvent*)event
+{
+    // No need to download keys, after a verification we already forced download
+    MXDeviceInfo *sendingDevice = [self.crypto.store deviceWithIdentityKey:event.senderKey];
+    if (!sendingDevice)
+    {
+        MXLogError(@"[MXSecretShareManager] canAcceptSecretSendEvent: Unknown sending device");
+        return NO;
+    }
+    
+    if (![sendingDevice.userId isEqualToString:self.crypto.mxSession.myUserId])
+    {
+        MXLogDebug(@"[MXSecretShareManager] canAcceptSecretSendEvent: Ignoring secret from another user");
+        return NO;
+    }
+    
+    if (!sendingDevice.trustLevel.isVerified)
+    {
+        MXLogDebug(@"[MXSecretShareManager] canAcceptSecretSendEvent: Ignoring secret from untrusted device");
+        return NO;
+    }
+    return YES;
+}
+
+
 @end

programming_language: Objective-C
filename: MXCrypto.m
change_type: ModificationType.MODIFY
diff: @@ -76,7 +76,7 @@ static NSString *const kMXCryptoOneTimeKeyClaimCompleteNotificationErrorKey
 NSTimeInterval kMXCryptoUploadOneTimeKeysPeriod = 60.0; // one minute
 NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
 
-@interface MXCrypto ()
+@interface MXCrypto () <MXUnrequestedForwardedRoomKeyManagerDelegate>
 {
     // MXEncrypting instance for each room.
     NSMutableDictionary<NSString*, id<MXEncrypting>> *roomEncryptors;
@@ -107,6 +107,9 @@ NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
     // The manager for incoming room key requests
     MXIncomingRoomKeyRequestManager *incomingRoomKeyRequestManager;
     
+    // The manager for unrequested m.forwarded_room_keys
+    MXUnrequestedForwardedRoomKeyManager *unrequestedForwardedRoomKeyManager;
+    
     // The date of the last time we forced establishment
     // of a new session for each user:device.
     MXUsersDevicesMap<NSDate*> *lastNewSessionForcedDates;
@@ -434,6 +437,9 @@ NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
 
         [self->outgoingRoomKeyRequestManager close];
         self->outgoingRoomKeyRequestManager = nil;
+        
+        [self->unrequestedForwardedRoomKeyManager close];
+        self->outgoingRoomKeyRequestManager = nil;
 
         if (deleteStore)
         {
@@ -983,6 +989,7 @@ NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
         {
             [self maybeUploadOneTimeKeys:nil failure:nil];
             [self->incomingRoomKeyRequestManager processReceivedRoomKeyRequests];
+            [self->unrequestedForwardedRoomKeyManager processUnrequestedKeys];
         }
     });
 
@@ -2044,6 +2051,9 @@ NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
                                          cryptoStore:_store];
 
         incomingRoomKeyRequestManager = [[MXIncomingRoomKeyRequestManager alloc] initWithCrypto:self];
+        
+        unrequestedForwardedRoomKeyManager = [[MXUnrequestedForwardedRoomKeyManager alloc] init];
+        unrequestedForwardedRoomKeyManager.delegate = self;
 
         _keyVerificationManager = [[MXKeyVerificationManager alloc] initWithCrypto:self];
         
@@ -2583,6 +2593,11 @@ NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
     [outgoingRoomKeyRequestManager cancelRoomKeyRequest:requestBody];
 }
 
+- (void)handleUnrequestedRoomKeyInfo:(MXRoomKeyInfo *)keyInfo senderId:(NSString *)senderId senderKey:(NSString *)senderKey
+{
+    [unrequestedForwardedRoomKeyManager addPendingKeyWithKeyInfo:keyInfo senderId:senderId senderKey:senderKey];
+}
+
 - (NSDictionary*)buildMegolmKeyForwardingMessage:(NSString*)roomId senderKey:(NSString*)senderKey sessionId:(NSString*)sessionId  chainIndex:(NSNumber*)chainIndex
 {
     NSDictionary *key = [self.olmDevice getInboundGroupSessionKey:roomId senderKey:senderKey sessionId:sessionId chainIndex:chainIndex];
@@ -2802,16 +2817,16 @@ NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
  */
 - (void)onRoomMembership:(MXEvent*)event roomState:(MXRoomState*)roomState
 {
-    id<MXEncrypting> alg = roomEncryptors[event.roomId];
-    if (!alg)
-    {
-        // No encrypting in this room
-        return;
-    }
-
     // Check whether we have to track the devices for this user.
     BOOL shouldTrack = NO;
     NSString *userId = event.stateKey;
+    
+    MXRoomMemberEventContent *content = [MXRoomMemberEventContent modelFromJSON:event.content];
+    if ([userId isEqualToString:self.mxSession.credentials.userId] && [content.membership isEqualToString:kMXMembershipStringInvite])
+    {
+        [unrequestedForwardedRoomKeyManager onRoomInviteWithRoomId:event.roomId senderId:event.sender];
+    }
+    
     MXRoomMember *member = [roomState.members memberWithUserId:userId];
     if (member)
     {
@@ -3264,6 +3279,25 @@ NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour
     }];
 }
 
+#pragma mark - MXUnrequestedForwardedRoomKeyManagerDelegate
+
+- (void)downloadDeviceKeysWithUserId:(NSString *)userId completion:(void (^)(MXUsersDevicesMap<MXDeviceInfo *> *))completion
+{
+    [self downloadKeys:@[userId] forceDownload:YES success:^(MXUsersDevicesMap<MXDeviceInfo *> *usersDevicesInfoMap, NSDictionary<NSString *,MXCrossSigningInfo *> *crossSigningKeysMap) {
+        completion(usersDevicesInfoMap);
+    } failure:^(NSError *error) {
+        MXLogError(@"[MXCrypto]: Failed downloading keys for key forward manager");
+        completion([[MXUsersDevicesMap alloc] init]);
+    }];
+}
+
+- (void)acceptRoomKeyWithKeyInfo:(MXRoomKeyInfo *)keyInfo
+{
+    id<MXDecrypting> decryptor = [self getRoomDecryptor:keyInfo.roomId algorithm:keyInfo.algorithm];
+    MXRoomKeyResult *key = [[MXRoomKeyResult alloc] initWithType:MXRoomKeyTypeUnsafe info:keyInfo];
+    [decryptor onRoomKey:key];
+}
+
 #endif
 
 @end

programming_language: Objective-C
filename: MXCrypto_Private.h
change_type: ModificationType.MODIFY
diff: @@ -33,6 +33,8 @@
 
 #import "MXCrypto.h"
 
+@class MXRoomKeyInfo;
+
 /**
  The `MXCrypto_Private` extension exposes internal operations.
  
@@ -224,6 +226,15 @@
 // Create a message to forward a megolm session
 - (NSDictionary*)buildMegolmKeyForwardingMessage:(NSString*)roomId senderKey:(NSString*)senderKey sessionId:(NSString*)sessionId chainIndex:(NSNumber*)chainIndex;
 
+/**
+ Handle forwarded room key that was not requested by this device
+ 
+ @param keyInfo details about the key
+ @param senderId userId of the person who sent us the key
+ @param senderKey identity of the person who sent us the room key
+ */
+- (void)handleUnrequestedRoomKeyInfo:(MXRoomKeyInfo *)keyInfo senderId:(NSString *)senderId senderKey:(NSString *)senderKey;
+
 @end
 
 #endif

programming_language: Objective-C
filename: MXOlmDevice.h
change_type: ModificationType.MODIFY
diff: @@ -241,7 +241,8 @@ Determine if an incoming messages is a prekey message matching an existing sessi
   forwardingCurve25519KeyChain:(NSArray<NSString *> *)forwardingCurve25519KeyChain
                    keysClaimed:(NSDictionary<NSString*, NSString*>*)keysClaimed
                   exportFormat:(BOOL)exportFormat
-                 sharedHistory:(BOOL)sharedHistory;
+                 sharedHistory:(BOOL)sharedHistory
+                     untrusted:(BOOL)untrusted;
 
 /**
  Add previously-exported inbound group sessions to the session store.

programming_language: Objective-C
filename: MXOlmDevice.m
change_type: ModificationType.MODIFY
diff: @@ -351,7 +351,8 @@ NSInteger const kMXInboundGroupSessionCacheSize = 100;
   forwardingCurve25519KeyChain:(NSArray<NSString *> *)forwardingCurve25519KeyChain
                    keysClaimed:(NSDictionary<NSString*, NSString*>*)keysClaimed
                   exportFormat:(BOOL)exportFormat
-                 sharedHistory:(BOOL)sharedHistory;
+                 sharedHistory:(BOOL)sharedHistory
+                     untrusted:(BOOL)untrusted
 {
     MXOlmInboundGroupSession *session;
     if (exportFormat)
@@ -372,13 +373,29 @@ NSInteger const kMXInboundGroupSessionCacheSize = 100;
     if (existingSession)
     {
         // If we already have this session, consider updating it
-        MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Update for megolm session %@|%@", senderKey, sessionId);
+        MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Considering updates for megolm session %@|%@", senderKey, sessionId);
 
-        // If our existing session is better, we keep it
-        if (existingSession.session.firstKnownIndex <= session.session.firstKnownIndex)
+        BOOL isExistingSessionBetter = existingSession.session.firstKnownIndex <= session.session.firstKnownIndex;
+        if (isExistingSessionBetter)
         {
-            MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Skip it. The index of the incoming session is higher (%@ vs %@)", @(session.session.firstKnownIndex), @(existingSession.session.firstKnownIndex));
-            return NO;
+            BOOL isNewSessionSafer = existingSession.isUntrusted && !session.isUntrusted;
+            if (!isNewSessionSafer)
+            {
+                MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Skip it. The index of the incoming session is higher (%@ vs %@)", @(session.session.firstKnownIndex), @(existingSession.session.firstKnownIndex));
+                return NO;
+            }
+            
+            if ([self connectsSession1:existingSession session2:session])
+            {
+                MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Skipping new session, and upgrading the safety of existing session");
+                [self upgradeSafetyForSession:existingSession];
+                return NO;
+            }
+            else
+            {
+                MXLogWarning(@"[MXOlmDevice] addInboundGroupSession: Recieved a safer but disconnected key, which will override the existing unsafe key");
+                existingSession = nil;
+            }
         }
     }
 
@@ -394,6 +411,7 @@ NSInteger const kMXInboundGroupSessionCacheSize = 100;
     session.roomId = roomId;
     session.keysClaimed = keysClaimed;
     session.forwardingCurve25519KeyChain = forwardingCurve25519KeyChain;
+    session.untrusted = untrusted;
     
     // If we already have a session stored, the sharedHistory flag will not be overwritten
     if (!existingSession && MXSDKOptions.sharedInstance.enableRoomSharedHistoryOnInvite)
@@ -406,6 +424,30 @@ NSInteger const kMXInboundGroupSessionCacheSize = 100;
     return YES;
 }
 
+- (void)upgradeSafetyForSession:(MXOlmInboundGroupSession *)session
+{
+    [self.store performSessionOperationWithGroupSessionWithId:session.session.sessionIdentifier senderKey:session.senderKey block:^(MXOlmInboundGroupSession *inboundGroupSession) {
+        inboundGroupSession.untrusted = NO;
+    }];
+    if (MXSDKOptions.sharedInstance.enableGroupSessionCache)
+    {
+        @synchronized (self.inboundGroupSessionCache)
+        {
+            session.untrusted = NO;
+            [self.inboundGroupSessionCache put:session.session.sessionIdentifier object:session];
+        }
+    }
+}
+
+- (BOOL)connectsSession1:(MXOlmInboundGroupSession *)session1 session2:(MXOlmInboundGroupSession *)session2
+{
+    // `connects` function will be moved to libolm in the future to avoid having to export the session
+    NSUInteger lowestCommonIndex = MAX(session1.session.firstKnownIndex, session2.session.firstKnownIndex);
+    MXMegolmSessionData *export1 = [session1 exportSessionDataAtMessageIndex:lowestCommonIndex];
+    MXMegolmSessionData *export2 = [session2 exportSessionDataAtMessageIndex:lowestCommonIndex];
+    return [export1.sessionKey isEqualToString:export2.sessionKey];
+}
+
 - (NSArray<MXOlmInboundGroupSession *>*)importInboundGroupSessions:(NSArray<MXMegolmSessionData *>*)inboundGroupSessionsData;
 {
     NSMutableArray<MXOlmInboundGroupSession *> *sessions = [NSMutableArray arrayWithCapacity:inboundGroupSessionsData.count];

programming_language: Objective-C
filename: MXRoomKeyInfo.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,90 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+/// Domain object representing a room key and its parameters
+@objcMembers
+public class MXRoomKeyInfo: NSObject {
+    public let algorithm: String
+    public let sessionId: String
+    public let sessionKey: String
+    public let roomId: String
+    public let senderKey: String
+    public let forwardingKeyChain: [String]?
+    public let keysClaimed: [String: String]
+    public let exportFormat: Bool
+    public let sharedHistory: Bool
+    
+    public init(
+        algorithm: String,
+        sessionId: String,
+        sessionKey: String,
+        roomId: String,
+        senderKey: String,
+        forwardingKeyChain: [String]?,
+        keysClaimed: [String: String],
+        exportFormat: Bool,
+        sharedHistory: Bool
+    ) {
+        self.algorithm = algorithm
+        self.sessionId = sessionId
+        self.sessionKey = sessionKey
+        self.roomId = roomId
+        self.senderKey = senderKey
+        self.forwardingKeyChain = forwardingKeyChain
+        self.keysClaimed = keysClaimed
+        self.exportFormat = exportFormat
+        self.sharedHistory = sharedHistory
+        super.init()
+    }
+}
+
+extension MXRoomKeyInfo {
+    convenience init?(roomKey: MXRoomKeyEventContent, event: MXEvent) {
+        guard let senderKey = event.senderKey, let keysClaimed = event.keysClaimed as? [String: String] else {
+            return nil
+        }
+        
+        self.init(
+            algorithm: roomKey.algorithm,
+            sessionId: roomKey.sessionId,
+            sessionKey: roomKey.sessionKey,
+            roomId: roomKey.roomId,
+            senderKey: senderKey,
+            forwardingKeyChain: nil,
+            keysClaimed: keysClaimed,
+            exportFormat: false,
+            sharedHistory: roomKey.sharedHistory
+        )
+    }
+}
+
+extension MXRoomKeyInfo {
+    convenience init(forwardedRoomKey: MXForwardedRoomKeyEventContent) {
+        self.init(
+            algorithm: forwardedRoomKey.algorithm,
+            sessionId: forwardedRoomKey.sessionId,
+            sessionKey: forwardedRoomKey.sessionKey,
+            roomId: forwardedRoomKey.roomId,
+            senderKey: forwardedRoomKey.senderKey,
+            forwardingKeyChain: forwardedRoomKey.forwardingCurve25519KeyChain,
+            keysClaimed: ["ed25519": forwardedRoomKey.senderClaimedEd25519Key],
+            exportFormat: true,
+            sharedHistory: forwardedRoomKey.sharedHistory
+        )
+    }
+}

programming_language: Swift
filename: MXRoomKeyInfoFactory.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,100 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+@objcMembers
+public class MXRoomKeyInfoFactory: NSObject {
+    private let myUserId: String
+    private let store: MXCryptoStore
+    private let log = MXNamedLog(name: "MXRoomKeyFactory")
+    
+    public init(myUserId: String, store: MXCryptoStore) {
+        self.myUserId = myUserId
+        self.store = store
+    }
+    
+    public func roomKey(for event: MXEvent) -> MXRoomKeyResult? {
+        if event.eventType == .roomKey {
+            return roomKeyEventInfo(for: event)
+        } else if event.eventType == .roomForwardedKey {
+            return forwardedRoomKeyEventInfo(for: event)
+        } else {
+            log.error("Unknown event type", context: event.eventType)
+            return nil
+        }
+    }
+    
+    private func roomKeyEventInfo(for event: MXEvent) -> MXRoomKeyResult? {
+        guard
+            let content = MXRoomKeyEventContent(fromJSON: event.content),
+            let info = MXRoomKeyInfo(roomKey: content, event: event)
+        else {
+            log.error("Invalid room key")
+            return nil
+        }
+        
+        return .init(type: .safe, info: info)
+    }
+    
+    private func forwardedRoomKeyEventInfo(for event: MXEvent) -> MXRoomKeyResult? {
+        guard let eventSenderKey = event.senderKey else {
+            log.error("Unknown event sender")
+            return nil
+        }
+        
+        guard let content = MXForwardedRoomKeyEventContent(fromJSON: event.content) else {
+            log.error("Invalid forwarded key")
+            return nil
+        }
+        
+        content.forwardingCurve25519KeyChain += [eventSenderKey]
+
+        return .init(
+            type: keyType(for: content, senderKey: eventSenderKey),
+            info: .init(forwardedRoomKey: content)
+        )
+    }
+    
+    private func keyType(for content: MXForwardedRoomKeyEventContent, senderKey: String) -> MXRoomKeyType {
+        if !hasPendingRequest(for: content) {
+            log.debug("Key was not requested")
+            return .unrequested
+        } else if isMyVerifiedDevice(identityKey: senderKey) {
+            return .safe
+        } else {
+            log.debug("Key forward is not from my verified device")
+            return .unsafe
+        }
+    }
+    
+    private func isMyVerifiedDevice(identityKey: String) -> Bool {
+        guard let device = store.device(withIdentityKey: identityKey) else {
+            return false
+        }
+        return device.userId == myUserId && device.trustLevel.isVerified
+    }
+    
+    private func hasPendingRequest(for content: MXForwardedRoomKeyEventContent) -> Bool {
+        let request = store.outgoingRoomKeyRequest(withRequestBody: [
+            "room_id": content.roomId,
+            "algorithm": content.algorithm,
+            "sender_key": content.senderKey,
+            "session_id": content.sessionId
+        ])
+        return request != nil
+    }
+}

programming_language: Swift
filename: MXRoomKeyResult.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,34 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+@objc public enum MXRoomKeyType: Int {
+    case safe
+    case unsafe
+    case unrequested
+}
+
+@objcMembers
+public class MXRoomKeyResult: NSObject {
+    public let type: MXRoomKeyType
+    public let info: MXRoomKeyInfo
+    
+    public init(type: MXRoomKeyType, info: MXRoomKeyInfo) {
+        self.type = type
+        self.info = info
+    }
+}

programming_language: Swift
filename: MXSASTransaction.m
change_type: ModificationType.MODIFY
diff: @@ -363,6 +363,8 @@ static NSArray<MXEmojiRepresentation*> *kSasEmojis;
 
         __block MXTransactionCancelCode *cancelCode;
         dispatch_group_t group = dispatch_group_create();
+        
+        MXCrossSigningKey *otherUserMasterKeys= [self.manager.crypto crossSigningKeysForUser:self.otherDevice.userId].masterKeys;
 
         for (NSString *keyFullId in self.theirMac.mac)
         {
@@ -372,6 +374,13 @@ static NSArray<MXEmojiRepresentation*> *kSasEmojis;
             MXDeviceInfo *device = [self.manager.crypto deviceWithDeviceId:key.keyId ofUser:self.otherDevice.userId];
             if (device)
             {
+                if ([device.deviceId isEqualToString:otherUserMasterKeys.keys])
+                {
+                    MXLogWarning(@"[MXKeyVerification][MXSASTransaction] verifyMacs: Device id should not be the same as master key");
+                    cancelCode = MXTransactionCancelCode.invalidMessage;
+                    break;
+                }
+                
                 if ([key.value isEqualToString:[self macUsingAgreedMethod:device.keys[keyFullId]
                                                                      info:[NSString stringWithFormat:@"%@%@", baseInfo, keyFullId]]])
                 {
@@ -398,7 +407,6 @@ static NSArray<MXEmojiRepresentation*> *kSasEmojis;
             else
             {
                 // This key is maybe a cross-signing master key
-                MXCrossSigningKey *otherUserMasterKeys= [self.manager.crypto crossSigningKeysForUser:self.otherDevice.userId].masterKeys;
                 if (otherUserMasterKeys)
                 {
                     // Check MAC with user's MSK keys

programming_language: Objective-C
filename: MXSession.m
change_type: ModificationType.MODIFY
diff: @@ -1964,16 +1964,25 @@ typedef void (^MXOnResumeDone)(void);
 
 - (void)handleToDeviceEvents:(NSArray<MXEvent *> *)events  onComplete:(void (^)(void))onComplete
 {
-    if (events.count == 0)
+    NSMutableArray *supportedEvents = [NSMutableArray arrayWithCapacity:events.count];
+    for (MXEvent *event in events)
+    {
+        if ([MXTools isSupportedToDeviceEvent:event])
+        {
+            [supportedEvents addObject:event];
+        }
+    }
+
+    if (supportedEvents.count == 0)
     {
         onComplete();
         return;
     }
     
-    [self decryptEvents:events inTimeline:nil onComplete:^(NSArray<MXEvent *> *failedEvents) {
+    [self decryptEvents:supportedEvents inTimeline:nil onComplete:^(NSArray<MXEvent *> *failedEvents) {
         dispatch_group_t dispatchGroup = dispatch_group_create();
         
-        for (MXEvent *event in events)
+        for (MXEvent *event in supportedEvents)
         {
             if (!event.decryptionError)
             {

programming_language: Objective-C
filename: MatrixSDK.h
change_type: ModificationType.MODIFY
diff: @@ -171,6 +171,8 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion;
 #import "MXCachedSyncResponse.h"
 #import "MXBackgroundCryptoStore.h"
 #import "MXSharedHistoryKeyService.h"
+#import "MXRoomKeyEventContent.h"
+#import "MXForwardedRoomKeyEventContent.h"
 
 //  Sync response models
 #import "MXSyncResponse.h"

programming_language: Objective-C
filename: Dictionary.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,31 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+internal extension Dictionary {
+    func adding(key: Key, value: Value) -> [Key: Value] {
+        var dict = self
+        dict[key] = value
+        return dict
+    }
+    
+    func removing(key: Key) -> [Key: Value] {
+        var dict = self
+        dict[key] = nil
+        return dict
+    }
+}

programming_language: Swift
filename: MXDateProvider.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,27 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+protocol MXDateProviding {
+    func currentDate() -> Date
+}
+
+struct MXDateProvider: MXDateProviding {
+    func currentDate() -> Date {
+        Date()
+    }
+}

programming_language: Swift
filename: MXLRUCache.m
change_type: ModificationType.MODIFY
diff: @@ -149,6 +149,11 @@
                 [cachedObjects addObject:item];
                 [cachedKeys addObject:key];
             }
+            else
+            {
+                MXLRUCacheItem* item = [cachedObjects objectAtIndex:pos];
+                item.object = object;
+            }
         }
     }
 }

programming_language: Objective-C
filename: MXTools.swift
change_type: ModificationType.MODIFY
diff: @@ -44,4 +44,33 @@ public extension MXTools {
         }
         return urlString
     }
+
+    @objc
+    /// Checks whether a given to-device event is supported or not.
+    /// - Parameter event: Event to be checked
+    /// - Returns: `true` if the event is supported, otherwise `false`
+    static func isSupportedToDeviceEvent(_ event: MXEvent) -> Bool {
+        if event.isEncrypted {
+            // only support OLM encrypted events
+            let algorithm = event.wireContent["algorithm"] as? String
+            guard algorithm == kMXCryptoOlmAlgorithm else {
+                MXLog.debug("[MXTools] isSupportedToDeviceEvent: not supported event encrypted with other than OLM algorithm: \(String(describing: algorithm))")
+                return false
+            }
+        } else {
+            // define unsupported plain event types
+            let unsupportedPlainEvents = Set([
+                MXEventType.roomKey.identifier,
+                MXEventType.roomForwardedKey.identifier,
+                MXEventType.secretSend.identifier
+            ])
+            // make sure that the event type is supported
+            if unsupportedPlainEvents.contains(event.type) {
+                MXLog.debug("[MXTools] isSupportedToDeviceEvent: not supported plain event with type: \(String(describing: event.type))")
+                return false
+            }
+        }
+
+        return true
+    }
 }

programming_language: Swift
filename: MXMegolmDecryptionUnitTests.swift
change_type: ModificationType.MODIFY
diff: @@ -35,7 +35,8 @@ class MXMegolmDecryptionUnitTests: XCTestCase {
             forwardingCurve25519KeyChain: [String]!,
             keysClaimed: [String : String]!,
             exportFormat: Bool,
-            sharedHistory: Bool
+            sharedHistory: Bool,
+            untrusted: Bool
         ) -> Bool {
             sessions.append(
                 .init(sharedHistory: sharedHistory)
@@ -137,7 +138,9 @@ class MXMegolmDecryptionUnitTests: XCTestCase {
         session.historyVisibility = kMXRoomHistoryVisibilityWorldReadable
         
         for (eventValue, expectedValue) in eventToExpectation {
-            let event = makeRoomKeyEvent(sharedHistory: eventValue)
+            let event = MXEvent.roomKeyFixture(
+                sharedHistory: eventValue
+            )
             device.sessions = []
             
             decryption.onRoomKeyEvent(event)
@@ -213,29 +216,4 @@ class MXMegolmDecryptionUnitTests: XCTestCase {
             XCTAssertEqual(hasSharedHistory, expectedValue)
         }
     }
-    
-    // MARK: - Helpers
-    
-    /// Create a room key event with some random but valid data that can be used to create a new inbound session.
-    private func makeRoomKeyEvent(sharedHistory: Bool? = nil) -> MXEvent? {
-        let event = MXEvent(fromJSON: [
-            "sender_key": senderKey,
-        ])
-        
-        var content: [String: Any] = [
-            "room_id": roomId1,
-            "session_id": sessionId1,
-            "session_key": "123",
-            "algorithm": "456",
-        ]
-        if let sharedHistory = sharedHistory {
-            content["org.matrix.msc3061.shared_history"] = sharedHistory
-        }
-        
-        let result = MXEventDecryptionResult()
-        result.senderCurve25519Key = "XYZ"
-        result.clearEvent = ["content": content]
-        event?.setClearData(result)
-        return event
-    }
 }

programming_language: Swift
filename: MXMemoryCryptoStore.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,511 @@
+//
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import OLMKit
+
+public class MXMemoryCryptoStore: NSObject, MXCryptoStore {
+
+    private static var stores: [MXCredentials: MXMemoryCryptoStore] = [:]
+
+    private let credentials: MXCredentials
+    private var storeAccount: Account?
+    private var devices: [String: [MXDeviceInfo]] = [:]
+    private var algorithms: [String: RoomAlgorithm] = [:]
+    private var inboundSessions: [InboundSession] = []
+    private var outboundSessions: [String: MXOlmOutboundGroupSession] = [:]
+    private var secrets: [String: String] = [:]
+    private var incomingRoomKeyRequestsMap: [String: MXIncomingRoomKeyRequest] = [:]
+    private var outgoingRoomKeyRequests: [String: MXOutgoingRoomKeyRequest] = [:]
+    private var olmSessions: [OlmSessionMapKey: MXOlmSession] = [:]
+    private var crossSigningKeysMap: [String: MXCrossSigningInfo] = [:]
+    private var sharedOutboundSessions: [SharedOutboundSession] = []
+
+    // MARK: - MXCryptoStore
+
+    public required init!(credentials: MXCredentials!) {
+        self.credentials = credentials
+        storeAccount = Account()
+        storeAccount?.userId = credentials.userId
+        storeAccount?.deviceId = credentials.deviceId
+        storeAccount?.cryptoVersion = MXCryptoVersion(rawValue: MXCryptoVersion.versionCount.rawValue - 1) ?? .versionUndefined
+        super.init()
+    }
+
+    public static func hasData(for credentials: MXCredentials!) -> Bool {
+        stores[credentials] != nil
+    }
+
+    public static func createStore(with credentials: MXCredentials!) -> Self! {
+        if let existingStore = stores[credentials] as? Self {
+            return existingStore
+        }
+        if let newStore = Self(credentials: credentials) {
+            stores[credentials] = newStore
+            return newStore
+        }
+        return nil
+    }
+
+    public static func delete(with credentials: MXCredentials!) {
+        stores.removeValue(forKey: credentials)
+    }
+
+    public static func deleteAllStores() {
+        stores.removeAll()
+    }
+
+    public static func deleteReadonlyStore(with credentials: MXCredentials!) {
+        // no-op
+    }
+
+    public func open(_ onComplete: (() -> Void)!, failure: ((Error?) -> Void)!) {
+        onComplete?()
+    }
+
+    // MARK: - Device ID
+
+    public func storeDeviceId(_ deviceId: String!) {
+        storeAccount?.deviceId = deviceId
+    }
+
+    public func deviceId() -> String! {
+        storeAccount?.deviceId
+    }
+
+    // MARK: - Account
+
+    public func setAccount(_ account: OLMAccount!) {
+        storeAccount?.olmAccount = account
+    }
+
+    public func account() -> OLMAccount! {
+        storeAccount?.olmAccount
+    }
+
+    public func performAccountOperation(_ block: ((OLMAccount?) -> Void)!) {
+        block?(storeAccount?.olmAccount)
+    }
+
+    // MARK: - Device Sync Token
+
+    public func storeDeviceSyncToken(_ deviceSyncToken: String!) {
+        storeAccount?.deviceSyncToken = deviceSyncToken
+    }
+
+    public func deviceSyncToken() -> String! {
+        storeAccount?.deviceSyncToken
+    }
+
+    // MARK: - Devices
+
+    public func storeDevice(forUser userId: String!, device: MXDeviceInfo!) {
+        if devices[userId] == nil {
+            devices[userId] = []
+        }
+        devices[userId]?.append(device)
+    }
+
+    public func device(withDeviceId deviceId: String!, forUser userId: String!) -> MXDeviceInfo! {
+        devices[userId]?.first { $0.deviceId == deviceId }
+    }
+
+    public func device(withIdentityKey identityKey: String!) -> MXDeviceInfo! {
+        Array(devices.values).flatMap { $0 }.first { $0.identityKey == identityKey }
+    }
+
+    public func storeDevices(forUser userId: String!, devices: [String : MXDeviceInfo]!) {
+        if self.devices[userId] != nil {
+            // Reset all previously stored devices for this user
+            self.devices.removeValue(forKey: userId)
+        }
+
+        self.devices[userId] = Array(devices.values)
+    }
+
+    public func devices(forUser userId: String!) -> [String : MXDeviceInfo]! {
+        let devices = devices[userId] ?? []
+
+        var result: [String: MXDeviceInfo] = [:]
+
+        for device in devices {
+            result[device.deviceId] = device
+        }
+
+        return result
+    }
+
+    // MARK: - Device Tracking Status
+
+    public func deviceTrackingStatus() -> [String : NSNumber]! {
+        storeAccount?.deviceTrackingStatus
+    }
+
+    public func storeDeviceTrackingStatus(_ statusMap: [String : NSNumber]!) {
+        storeAccount?.deviceTrackingStatus = statusMap
+    }
+
+    // MARK: - Cross Signing Keys
+
+    public func storeCrossSigningKeys(_ crossSigningInfo: MXCrossSigningInfo!) {
+        crossSigningKeysMap[crossSigningInfo.userId] = crossSigningInfo
+    }
+
+    public func crossSigningKeys(forUser userId: String!) -> MXCrossSigningInfo! {
+        crossSigningKeysMap[userId]
+    }
+
+    public func crossSigningKeys() -> [MXCrossSigningInfo]! {
+        Array(crossSigningKeysMap.values)
+    }
+
+    // MARK: - Room Algorithm
+
+    public func storeAlgorithm(forRoom roomId: String!, algorithm: String!) {
+        algorithms[roomId] = RoomAlgorithm(algorithm: algorithm)
+    }
+
+    public func algorithm(forRoom roomId: String!) -> String! {
+        algorithms[roomId]?.algorithm
+    }
+
+    // MARK: - OLM Session
+
+    public func store(_ session: MXOlmSession!, forDevice deviceKey: String!) {
+        let key = OlmSessionMapKey(sessionId: session.session.sessionIdentifier(), deviceKey: deviceKey)
+        olmSessions[key] = session
+    }
+
+    public func session(withDevice deviceKey: String!, andSessionId sessionId: String!) -> MXOlmSession! {
+        let key = OlmSessionMapKey(sessionId: sessionId, deviceKey: deviceKey)
+        return olmSessions[key]
+    }
+
+    public func performSessionOperation(withDevice deviceKey: String!, andSessionId sessionId: String!, block: ((MXOlmSession?) -> Void)!) {
+        let session = session(withDevice: deviceKey, andSessionId: sessionId)
+        block?(session)
+    }
+
+    public func sessions(withDevice deviceKey: String!) -> [MXOlmSession]! {
+        Array(olmSessions.filter { $0.key.deviceKey == deviceKey }.values)
+    }
+
+    // MARK: - Inbound Group Sessions
+
+    public func store(_ sessions: [MXOlmInboundGroupSession]!) {
+        inboundSessions.append(contentsOf: sessions.map { InboundSession(session: $0) } )
+    }
+
+    public func inboundGroupSession(withId sessionId: String!, andSenderKey senderKey: String!) -> MXOlmInboundGroupSession! {
+        inboundSessions.first { $0.sessionId == sessionId && $0.session.senderKey == senderKey }?.session
+    }
+
+    public func performSessionOperationWithGroupSession(withId sessionId: String!, senderKey: String!, block: ((MXOlmInboundGroupSession?) -> Void)!) {
+        let session = inboundGroupSession(withId: sessionId, andSenderKey: senderKey)
+        block?(session)
+    }
+
+    public func inboundGroupSessions() -> [MXOlmInboundGroupSession]! {
+        inboundSessions.map { $0.session }
+    }
+
+    public func inboundGroupSessions(withSessionId sessionId: String!) -> [MXOlmInboundGroupSession]! {
+        inboundSessions.filter { $0.sessionId == sessionId }.map { $0.session }
+    }
+
+    public func removeInboundGroupSession(withId sessionId: String!, andSenderKey senderKey: String!) {
+        inboundSessions.removeAll { $0.sessionId == sessionId && $0.session.senderKey == senderKey }
+    }
+
+    // MARK: - Outbound Group Sessions
+
+    public func store(_ session: OLMOutboundGroupSession!, withRoomId roomId: String!) -> MXOlmOutboundGroupSession! {
+        let creationTime: TimeInterval
+
+        if let existingSession = outboundSessions[roomId],
+           existingSession.sessionId == session.sessionIdentifier() {
+            // Update the existing one
+            creationTime = existingSession.creationTime
+        } else {
+            creationTime = Date().timeIntervalSince1970
+        }
+
+        if let newSession = MXOlmOutboundGroupSession(session: session, roomId: roomId, creationTime: creationTime) {
+            outboundSessions[roomId] = newSession
+            return newSession
+        }
+
+        return nil
+    }
+
+    public func outboundGroupSession(withRoomId roomId: String!) -> MXOlmOutboundGroupSession! {
+        outboundSessions[roomId]
+    }
+
+    public func outboundGroupSessions() -> [MXOlmOutboundGroupSession]! {
+        Array(outboundSessions.values)
+    }
+
+    public func removeOutboundGroupSession(withRoomId roomId: String!) {
+        outboundSessions.removeValue(forKey: roomId)
+    }
+
+    // MARK: - Shared Devices
+
+    public func storeSharedDevices(_ devices: MXUsersDevicesMap<NSNumber>!, messageIndex: UInt, forOutboundGroupSessionInRoomWithId roomId: String!, sessionId: String!) {
+        for userId in devices.userIds() {
+            for deviceId in devices.deviceIds(forUser: userId) {
+                guard let device = device(withDeviceId: deviceId, forUser: userId) else {
+                    continue
+                }
+
+                let session = SharedOutboundSession(roomId: roomId, sessionId: sessionId, device: device, messageIndex: messageIndex)
+                sharedOutboundSessions.append(session)
+            }
+        }
+    }
+
+    public func sharedDevicesForOutboundGroupSessionInRoom(withId roomId: String!, sessionId: String!) -> MXUsersDevicesMap<NSNumber>! {
+        let result = MXUsersDevicesMap<NSNumber>()
+
+        let sessions = sharedOutboundSessions.filter { $0.roomId == roomId && $0.sessionId == sessionId }
+
+        for session in sessions {
+            result.setObject(NSNumber(value: session.messageIndex),
+                             forUser: session.device.userId,
+                             andDevice: session.device.deviceId)
+        }
+
+        return result
+    }
+
+    public func messageIndexForSharedDeviceInRoom(withId roomId: String!, sessionId: String!, userId: String!, deviceId: String!) -> NSNumber! {
+        guard let index = sharedOutboundSessions.first(where: { $0.roomId == roomId
+            && $0.sessionId == sessionId
+            && $0.device.deviceId == deviceId })?.messageIndex else {
+            return nil
+        }
+        return NSNumber(value: index)
+    }
+
+    // MARK: - Backup Markers
+
+    public var backupVersion: String! {
+        get {
+            storeAccount?.backupVersion
+        } set {
+            storeAccount?.backupVersion = newValue
+        }
+    }
+
+    public func resetBackupMarkers() {
+        inboundSessions.forEach { $0.backedUp = false }
+    }
+
+    public func markBackupDone(for sessions: [MXOlmInboundGroupSession]!) {
+        for session in sessions {
+            inboundSessions.filter({ $0.sessionId == session.session.sessionIdentifier() }).forEach { $0.backedUp = true }
+        }
+    }
+
+    public func inboundGroupSessions(toBackup limit: UInt) -> [MXOlmInboundGroupSession]! {
+        let toBackup = inboundSessions.filter { !$0.backedUp }
+        if toBackup.isEmpty {
+            return []
+        }
+        let toDrop = toBackup.count > limit ? toBackup.count - Int(limit) : 0
+        return toBackup.dropLast(toDrop).map { $0.session }
+    }
+
+    public func inboundGroupSessionsCount(_ onlyBackedUp: Bool) -> UInt {
+        UInt(onlyBackedUp ? inboundSessions.filter { $0.backedUp }.count : inboundSessions.count)
+    }
+
+    // MARK: - Outgoing Room Key Requests
+
+    public func outgoingRoomKeyRequest(withRequestBody requestBody: [AnyHashable : Any]!) -> MXOutgoingRoomKeyRequest! {
+        outgoingRoomKeyRequests.first(where: { NSDictionary(dictionary: $1.requestBody).isEqual(to: requestBody) })?.value
+    }
+
+    public func outgoingRoomKeyRequest(with state: MXRoomKeyRequestState) -> MXOutgoingRoomKeyRequest! {
+        outgoingRoomKeyRequests.first(where: { $0.value.state == state })?.value
+    }
+
+    public func allOutgoingRoomKeyRequests(with state: MXRoomKeyRequestState) -> [MXOutgoingRoomKeyRequest]! {
+        Array(outgoingRoomKeyRequests.filter { $1.state == state }.values)
+    }
+
+    public func allOutgoingRoomKeyRequests(withRoomId roomId: String!, sessionId: String!, algorithm: String!, senderKey: String!) -> [MXOutgoingRoomKeyRequest]! {
+        Array(outgoingRoomKeyRequests.filter {
+            $1.roomId == roomId
+            && $1.sessionId == sessionId
+            && $1.algorithm == algorithm
+            && $1.senderKey == senderKey
+        }.values)
+    }
+
+    public func store(_ request: MXOutgoingRoomKeyRequest!) {
+        outgoingRoomKeyRequests[request.requestId] = request
+    }
+
+    public func update(_ request: MXOutgoingRoomKeyRequest!) {
+        outgoingRoomKeyRequests[request.requestId] = request
+    }
+
+    public func deleteOutgoingRoomKeyRequest(withRequestId requestId: String!) {
+        outgoingRoomKeyRequests.removeValue(forKey: requestId)
+    }
+
+    // MARK: - Incoming Room Key Requests
+
+    public func store(_ request: MXIncomingRoomKeyRequest!) {
+        incomingRoomKeyRequestsMap[request.requestId] = request
+    }
+
+    public func deleteIncomingRoomKeyRequest(_ requestId: String!, fromUser userId: String!, andDevice deviceId: String!) {
+        let toBeRemoved = incomingRoomKeyRequestsMap.filter { $1.requestId == requestId && $1.userId == userId && $1.deviceId == deviceId }
+        for identifier in toBeRemoved {
+            incomingRoomKeyRequestsMap.removeValue(forKey: identifier.key)
+        }
+    }
+
+    public func incomingRoomKeyRequest(withRequestId requestId: String!, fromUser userId: String!, andDevice deviceId: String!) -> MXIncomingRoomKeyRequest! {
+        incomingRoomKeyRequestsMap.first(where: { $1.requestId == requestId && $1.userId == userId && $1.deviceId == deviceId })?.value
+    }
+
+    public func incomingRoomKeyRequests() -> MXUsersDevicesMap<NSArray>! {
+        let result = MXUsersDevicesMap<NSMutableArray>()
+
+        for request in incomingRoomKeyRequestsMap {
+            if let requests = result.object(forDevice: request.value.deviceId, forUser: request.value.userId) {
+                requests.add(request.value)
+            } else {
+                let requests = NSMutableArray(object: request.value)
+                result.setObject(requests, forUser: request.value.userId, andDevice: request.value.deviceId)
+            }
+        }
+
+        return result as? MXUsersDevicesMap<NSArray>
+    }
+
+    // MARK: - Secrets
+
+    public func storeSecret(_ secret: String!, withSecretId secretId: String!) {
+        secrets[secretId] = secret
+    }
+
+    public func secret(withSecretId secretId: String!) -> String! {
+        secrets[secretId]
+    }
+
+    public func deleteSecret(withSecretId secretId: String!) {
+        secrets.removeValue(forKey: secretId)
+    }
+
+    // MARK: - Blacklist Unverified Devices
+
+    public var globalBlacklistUnverifiedDevices: Bool {
+        get {
+            storeAccount?.globalBlacklistUnverifiedDevices ?? false
+        } set {
+            storeAccount?.globalBlacklistUnverifiedDevices = newValue
+        }
+    }
+
+    public func blacklistUnverifiedDevices(inRoom roomId: String!) -> Bool {
+        algorithms[roomId]?.blacklistUnverifiedDevices ?? false
+    }
+
+    public func storeBlacklistUnverifiedDevices(inRoom roomId: String!, blacklist: Bool) {
+        if let algorithm = algorithms[roomId] {
+            algorithm.blacklistUnverifiedDevices = blacklist
+        } else {
+            algorithms[roomId] = RoomAlgorithm(algorithm: nil, blacklistUnverifiedDevices: blacklist)
+        }
+    }
+
+    // MARK: - Crypto Version
+
+    public var cryptoVersion: MXCryptoVersion {
+        get {
+            storeAccount?.cryptoVersion ?? .versionUndefined
+        } set {
+            storeAccount?.cryptoVersion = newValue
+        }
+    }
+
+}
+
+// MARK: - Models
+
+// MARK: InboundSession
+
+private class InboundSession {
+    let session: MXOlmInboundGroupSession
+    var backedUp: Bool
+
+    var sessionId: String {
+        session.session.sessionIdentifier()
+    }
+
+    init(session: MXOlmInboundGroupSession,
+         backedUp: Bool = false) {
+        self.session = session
+        self.backedUp = backedUp
+    }
+}
+
+// MARK: OlmSessionMapKey
+
+private struct OlmSessionMapKey: Hashable {
+    let sessionId: String
+    let deviceKey: String
+}
+
+// MARK: Account
+
+private struct Account {
+    var userId: String?
+    var deviceId: String?
+    var cryptoVersion: MXCryptoVersion = .versionUndefined
+    var deviceSyncToken: String?
+    var olmAccount: OLMAccount?
+    var backupVersion: String?
+    var globalBlacklistUnverifiedDevices: Bool = false
+    var deviceTrackingStatus: [String : NSNumber]?
+}
+
+// MARK: SharedOutboundSession
+
+private struct SharedOutboundSession {
+    let roomId: String
+    let sessionId: String
+    let device: MXDeviceInfo
+    let messageIndex: UInt
+}
+
+// MARK: RoomAlgorithm
+
+private class RoomAlgorithm {
+    let algorithm: String?
+    var blacklistUnverifiedDevices: Bool
+
+    init(algorithm: String?,
+         blacklistUnverifiedDevices: Bool = false) {
+        self.algorithm = algorithm
+        self.blacklistUnverifiedDevices = blacklistUnverifiedDevices
+    }
+}

programming_language: Swift
filename: MXForwardedRoomKeyEventContentUnitTests.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,121 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import XCTest
+@testable import MatrixSDK
+
+class MXForwardedRoomKeyEventContentUnitTests: XCTestCase {
+    
+    // MARK: - modelFromJSON
+    
+    func makeValidJSON() -> [String: Any] {
+        return [
+            "algorithm": "A",
+            "room_id": "B",
+            "sender_key": "C",
+            "session_id": "D",
+            "session_key": "E",
+            "sender_claimed_ed25519_key": "F",
+            "forwarding_curve25519_key_chain": ["G", "H"],
+            kMXSharedHistoryKeyName: false
+        ]
+    }
+    
+    func test_modelFromJSON_doesNotCreateWithMissingFields() {
+        XCTAssertNil(MXForwardedRoomKeyEventContent(
+            fromJSON: [:])
+        )
+        
+        XCTAssertNil(MXForwardedRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "algorithm"))
+        )
+        
+        XCTAssertNil(MXForwardedRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "room_id"))
+        )
+        
+        XCTAssertNil(MXForwardedRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "sender_key"))
+        )
+        
+        XCTAssertNil(MXForwardedRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "session_id"))
+        )
+        
+        XCTAssertNil(MXForwardedRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "session_key"))
+        )
+        
+        XCTAssertNil(MXForwardedRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "sender_claimed_ed25519_key"))
+        )
+    }
+    
+    func test_modelFromJSON_canCreateFromJSON() {
+        let content = MXForwardedRoomKeyEventContent(fromJSON: makeValidJSON())
+        
+        XCTAssertNotNil(content)
+        XCTAssertEqual(content?.algorithm, "A")
+        XCTAssertEqual(content?.roomId, "B")
+        XCTAssertEqual(content?.senderKey, "C")
+        XCTAssertEqual(content?.sessionId, "D")
+        XCTAssertEqual(content?.sessionKey, "E")
+        XCTAssertEqual(content?.senderClaimedEd25519Key, "F")
+        XCTAssertEqual(content?.forwardingCurve25519KeyChain, ["G", "H"])
+        XCTAssertEqual(content?.sharedHistory, false)
+    }
+    
+    func test_modelFromJSON_forwardingCurveChainDefaultsToEmpty() {
+        let json = makeValidJSON().removing(key: "forwarding_curve25519_key_chain")
+        let content = MXForwardedRoomKeyEventContent(fromJSON: json)
+        XCTAssertEqual(content?.forwardingCurve25519KeyChain, [])
+    }
+    
+    func test_modelFromJSON_sharedHistory() {
+        var json = makeValidJSON()
+        
+        json[kMXSharedHistoryKeyName] = true
+        let content1 = MXForwardedRoomKeyEventContent(fromJSON: json)
+        XCTAssertEqual(content1?.sharedHistory, true)
+        
+        json[kMXSharedHistoryKeyName] = false
+        let content2 = MXForwardedRoomKeyEventContent(fromJSON: json)
+        XCTAssertEqual(content2?.sharedHistory, false)
+        
+        json[kMXSharedHistoryKeyName] = nil
+        let content3 = MXForwardedRoomKeyEventContent(fromJSON: json)
+        XCTAssertEqual(content3?.sharedHistory, false)
+    }
+    
+    // MARK: - JSONDictionary
+    
+    func test_JSONDictionary_canExportJSON() {
+        let content = MXForwardedRoomKeyEventContent()
+        content.algorithm = "A"
+        content.roomId = "B"
+        content.senderKey = "C"
+        content.sessionId = "D"
+        content.sessionKey = "E"
+        content.senderClaimedEd25519Key = "F"
+        content.forwardingCurve25519KeyChain = ["G", "H"]
+        content.sharedHistory = false
+        
+        let json = content.jsonDictionary()
+        
+        XCTAssertEqual(json as? NSDictionary, makeValidJSON() as NSDictionary)
+    }
+}

programming_language: Swift
filename: MXRoomKeyEventContentUnitTests.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,98 @@
+//
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import XCTest
+@testable import MatrixSDK
+
+class MXRoomKeyEventContentUnitTests: XCTestCase {
+    
+    // MARK: - modelFromJSON
+    
+    func makeValidJSON() -> [String: Any] {
+        return [
+            "algorithm": "A",
+            "room_id": "B",
+            "session_id": "C",
+            "session_key": "D",
+            kMXSharedHistoryKeyName: false
+        ]
+    }
+    
+    func test_modelFromJSON_doesNotCreateWithMissingFields() {
+        XCTAssertNil(MXRoomKeyEventContent(
+            fromJSON: [:])
+        )
+        
+        XCTAssertNil(MXRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "algorithm"))
+        )
+        
+        XCTAssertNil(MXRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "room_id"))
+        )
+        
+        XCTAssertNil(MXRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "session_id"))
+        )
+        
+        XCTAssertNil(MXRoomKeyEventContent(
+            fromJSON: makeValidJSON().removing(key: "session_key"))
+        )
+    }
+    
+    func test_modelFromJSON_canCreateFromJSON() {
+        let content = MXRoomKeyEventContent(fromJSON: makeValidJSON())
+
+        XCTAssertNotNil(content)
+        XCTAssertEqual(content?.algorithm, "A")
+        XCTAssertEqual(content?.roomId, "B")
+        XCTAssertEqual(content?.sessionId, "C")
+        XCTAssertEqual(content?.sessionKey, "D")
+        XCTAssertEqual(content?.sharedHistory, false)
+    }
+
+    func test_modelFromJSON_sharedHistory() {
+        var json = makeValidJSON()
+
+        json[kMXSharedHistoryKeyName] = true
+        let content1 = MXRoomKeyEventContent(fromJSON: json)
+        XCTAssertEqual(content1?.sharedHistory, true)
+
+        json[kMXSharedHistoryKeyName] = false
+        let content2 = MXRoomKeyEventContent(fromJSON: json)
+        XCTAssertEqual(content2?.sharedHistory, false)
+
+        json[kMXSharedHistoryKeyName] = nil
+        let content3 = MXRoomKeyEventContent(fromJSON: json)
+        XCTAssertEqual(content3?.sharedHistory, false)
+    }
+
+    // MARK: - JSONDictionary
+
+    func test_JSONDictionary_canExportJSON() {
+        let content = MXRoomKeyEventContent()
+        content.algorithm = "A"
+        content.roomId = "B"
+        content.sessionId = "C"
+        content.sessionKey = "D"
+        content.sharedHistory = false
+
+        let json = content.jsonDictionary()
+
+        XCTAssertEqual(json as? NSDictionary, makeValidJSON() as NSDictionary)
+    }
+}

programming_language: Swift
filename: MXUnrequestedForwardedRoomKeyManagerUnitTests.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,237 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import XCTest
+@testable import MatrixSDK
+
+private let BobSenderKey = "BobDeviceCurveKey"
+
+class MXUnrequestedForwardedRoomKeyManagerUnitTests: XCTestCase {
+    class Delegate: MXUnrequestedForwardedRoomKeyManagerDelegate {
+        var stubbedUserKeys = [String: [MXDeviceInfo]]()
+        var spyKeys = [MXRoomKeyInfo]()
+        
+        func downloadDeviceKeys(userId: String, completion: @escaping (MXUsersDevicesMap<MXDeviceInfo>) -> Void) {
+            let map = MXUsersDevicesMap<MXDeviceInfo>()
+            for (userId, devices) in stubbedUserKeys {
+                for device in devices {
+                    map.setObject(device, forUser: userId, andDevice: device.deviceId)
+                }
+            }
+            completion(map)
+        }
+        
+        func acceptRoomKey(keyInfo: MXRoomKeyInfo) {
+            spyKeys.append(keyInfo)
+        }
+    }
+    
+    class StubDateProvider: MXDateProviding {
+        var stubbedDate: Date?
+        func currentDate() -> Date {
+            return stubbedDate ?? Date()
+        }
+    }
+    
+    var dateProvider: StubDateProvider!
+    var manager: MXUnrequestedForwardedRoomKeyManager!
+    var delegate: Delegate!
+    
+    override func setUp() {
+        dateProvider = StubDateProvider()
+        manager = MXUnrequestedForwardedRoomKeyManager(dateProvider: dateProvider)
+        delegate = Delegate()
+        manager.delegate = delegate
+        
+        delegate.stubbedUserKeys = [
+            "Bob": [
+                MXDeviceInfo(fromJSON: [
+                    "user_id": "Bob",
+                    "device_id": "BobDevice",
+                    "keys": [
+                        "ed25519:BobDevice": "BobDeviceEdKey",
+                        "curve25519:BobDevice": BobSenderKey,
+                    ]
+                ])
+            ]
+        ]
+    }
+    
+    func test_processUnrequestedKeys_doesNothingIfNoKeys() {
+        manager.processUnrequestedKeys()
+        XCTAssertEqual(delegate.spyKeys, [])
+    }
+    
+    func test_processUnrequestedKeys_addsAllKeysForInvitedRooms() {
+        let keys = [
+            MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
+            MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
+            MXRoomKeyInfo.fixture(sessionId: "3", roomId: "B"),
+            MXRoomKeyInfo.fixture(sessionId: "4", roomId: "C"),
+        ]
+        for info in keys {
+            manager.addPendingKey(keyInfo: info, senderId: "Bob", senderKey: BobSenderKey)
+        }
+        manager.onRoomInvite(roomId: "A", senderId: "Bob")
+        manager.onRoomInvite(roomId: "C", senderId: "Bob")
+        
+        manager.processUnrequestedKeys()
+        
+        let sessionIds = delegate.spyKeys.map { $0.sessionId }
+        XCTAssertEqual(Set(sessionIds), ["1", "2", "4"])
+    }
+    
+    func test_processUnrequestedKeys_addsOnlyKeysFromRoomInviter() {
+        manager.addPendingKey(
+            keyInfo: MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
+            senderId: "Bob",
+            senderKey: "AliceKey"
+        )
+        manager.addPendingKey(
+            keyInfo: MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
+            senderId: "Bob",
+            senderKey: BobSenderKey
+        )
+        manager.addPendingKey(
+            keyInfo: MXRoomKeyInfo.fixture(sessionId: "3", roomId: "A"),
+            senderId: "Bob",
+            senderKey: "CharlieKey"
+        )
+        manager.onRoomInvite(roomId: "A", senderId: "Bob")
+
+        manager.processUnrequestedKeys()
+
+        let sessionIds = delegate.spyKeys.map { $0.sessionId }
+        XCTAssertEqual(Set(sessionIds), ["2"])
+    }
+    
+    func test_processUnrequestedKeys_doesNotAddKeysIfSenderNotValid() {
+        manager.addPendingKey(
+            keyInfo: MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
+            senderId: "Bob",
+            senderKey: "BobInvalidKey"
+        )
+        manager.onRoomInvite(roomId: "A", senderId: "Bob")
+
+        manager.processUnrequestedKeys()
+
+        XCTAssertEqual(delegate.spyKeys, [])
+    }
+    
+    func test_processUnrequestedKeys_removesProcessedKeys() {
+        delegate.spyKeys = []
+        manager.addPendingKey(
+            keyInfo: MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
+            senderId: "Bob",
+            senderKey: BobSenderKey
+        )
+        manager.processUnrequestedKeys()
+        XCTAssertEqual(delegate.spyKeys.count, 0)
+        
+        delegate.spyKeys = []
+        manager.onRoomInvite(roomId: "A", senderId: "Bob")
+        manager.processUnrequestedKeys()
+        XCTAssertEqual(delegate.spyKeys.count, 1)
+        
+        delegate.spyKeys = []
+        manager.processUnrequestedKeys()
+        XCTAssertEqual(delegate.spyKeys.count, 0)
+    }
+    
+    func test_processUnrequestedKeys_removesInvitesMoreThan10MinutesFromPresent() {
+        let minutesToRoom: [TimeInterval: String] = [
+            -10: "A",
+             -9: "B",
+             0: "C",
+             9: "D",
+             10: "E"
+        ]
+        
+        // First add all the keys
+        for (minutes, roomId) in minutesToRoom {
+            stubDate(timeInterval: minutes * 60)
+            manager.addPendingKey(
+                keyInfo: MXRoomKeyInfo.fixture(sessionId: roomId, roomId: roomId),
+                senderId: "Bob",
+                senderKey: BobSenderKey
+            )
+        }
+        
+        // Now add invites
+        for (minutes, roomId) in minutesToRoom {
+            stubDate(timeInterval: minutes * 60)
+            manager.onRoomInvite(roomId: roomId, senderId: "Bob")
+        }
+        
+        // Set the date to present
+        stubDate(timeInterval: 0)
+        
+        // Process invites
+        manager.processUnrequestedKeys()
+        
+        let sessionIds = delegate.spyKeys.map { $0.sessionId }
+        XCTAssertEqual(Set(sessionIds), ["B", "C", "D"])
+    }
+    
+    func test_processUnrequestedKeys_removesKeysOlderThan10MinutesOfInvite() {
+        stubDate(timeInterval: 0)
+        manager.addPendingKey(
+            keyInfo: MXRoomKeyInfo.fixture(sessionId: "1", roomId: "A"),
+            senderId: "Bob",
+            senderKey: BobSenderKey
+        )
+        stubDate(timeInterval: 1 * 60)
+        manager.addPendingKey(
+            keyInfo: MXRoomKeyInfo.fixture(sessionId: "2", roomId: "A"),
+            senderId: "Bob",
+            senderKey: BobSenderKey
+        )
+        stubDate(timeInterval: 10 * 60)
+        manager.onRoomInvite(roomId: "A", senderId: "Bob")
+        stubDate(timeInterval: 19 * 60)
+        
+        manager.processUnrequestedKeys()
+        
+        XCTAssertEqual(delegate.spyKeys.count, 1)
+        XCTAssertEqual(delegate.spyKeys.first?.sessionId, "2")
+    }
+    
+    // MARK: - Helpers
+    
+    func stubDate(timeInterval: TimeInterval) {
+        dateProvider.stubbedDate = Date(timeIntervalSince1970: timeInterval)
+    }
+}
+
+private extension MXRoomKeyInfo {
+    static func fixture(
+        sessionId: String,
+        roomId: String
+    ) -> MXRoomKeyInfo {
+        return MXRoomKeyInfo(
+            algorithm: "",
+            sessionId: sessionId,
+            sessionKey: "",
+            roomId: roomId,
+            senderKey: "",
+            forwardingKeyChain: nil,
+            keysClaimed: [:],
+            exportFormat: false,
+            sharedHistory: false
+        )
+    }
+}

programming_language: Swift
filename: MXOlmDeviceUnitTests.swift
change_type: ModificationType.MODIFY
diff: @@ -71,7 +71,8 @@ class MXOlmDeviceUnitTests: XCTestCase {
             forwardingCurve25519KeyChain: [],
             keysClaimed: [:],
             exportFormat: false,
-            sharedHistory: sharedHistory
+            sharedHistory: sharedHistory,
+            untrusted: false
         )
     }
     

programming_language: Swift
filename: MXRoomKeyInfoFactoryUnitTests.swift
change_type: ModificationType.ADD
diff: @@ -0,0 +1,184 @@
+// 
+// Copyright 2022 The Matrix.org Foundation C.I.C
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+import XCTest
+@testable import MatrixSDK
+
+class MXRoomKeyInfoFactoryUnitTests: XCTestCase {
+    
+    var store: MXMemoryCryptoStore!
+    var factory: MXRoomKeyInfoFactory!
+    override func setUp() {
+        store = MXMemoryCryptoStore(credentials: MXCredentials(homeServer: "", userId: "Alice", accessToken: nil))
+        factory = MXRoomKeyInfoFactory(myUserId: "Alice", store: store)
+        storeOutgoingKeyRequest(requestId: "1")
+    }
+    
+    // MARK: - Any event
+    
+    func test_roomKeyInfo_isNilForInvalidEvent() {
+        let info = factory.roomKey(for: MXEvent(fromJSON: [:]))
+        XCTAssertNil(info)
+    }
+    
+    // MARK: - Room key event
+    
+    func test_roomKeyInfo_createFromRoomKeyEvent() {
+        let event = MXEvent.roomKeyFixture()
+        
+        let key = factory.roomKey(for: event)
+        
+        let info = key?.info
+        XCTAssertNotNil(key)
+        XCTAssertEqual(key?.type, .safe)
+        XCTAssertEqual(info?.algorithm, "megolm")
+        XCTAssertEqual(info?.sessionId, "session1")
+        XCTAssertEqual(info?.sessionKey, "<key>")
+        XCTAssertEqual(info?.roomId, "!123:matrix.org")
+        XCTAssertEqual(info?.senderKey, "<sender_key>")
+        XCTAssertNil(info?.forwardingKeyChain)
+        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
+        XCTAssertEqual(info?.exportFormat, false)
+        XCTAssertEqual(info?.sharedHistory, false)
+    }
+    
+    // MARK: - Forwarded room key event
+    
+    func test_roomKeyInfo_isUnrequestedIfKeyNotRequested() {
+        store.deleteOutgoingRoomKeyRequest(withRequestId: "1")
+        storeDevice(userId: "Alice", trusted: true, identityKey: "AliceSender")
+        let event = MXEvent.forwardedRoomKeyFixture(
+            senderKey: "AliceSender"
+        )
+        
+        let key = factory.roomKey(for: event)
+        
+        let info = key?.info
+        XCTAssertNotNil(key)
+        XCTAssertEqual(key?.type, .unrequested)
+        XCTAssertEqual(info?.algorithm, "megolm")
+        XCTAssertEqual(info?.sessionId, "session1")
+        XCTAssertEqual(info?.sessionKey, "<key>")
+        XCTAssertEqual(info?.roomId, "!123:matrix.org")
+        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
+        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
+        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
+        XCTAssertEqual(info?.exportFormat, true)
+        XCTAssertEqual(info?.sharedHistory, false)
+    }
+    
+    func test_roomKeyInfo_isUnsafeIfNotFromMyself() {
+        storeDevice(userId: "Bob", trusted: true, identityKey: "AliceSender")
+        let event = MXEvent.forwardedRoomKeyFixture(
+            senderKey: "AliceSender"
+        )
+        
+        let key = factory.roomKey(for: event)
+        
+        let info = key?.info
+        XCTAssertNotNil(key)
+        XCTAssertEqual(key?.type, .unsafe)
+        XCTAssertEqual(info?.algorithm, "megolm")
+        XCTAssertEqual(info?.sessionId, "session1")
+        XCTAssertEqual(info?.sessionKey, "<key>")
+        XCTAssertEqual(info?.roomId, "!123:matrix.org")
+        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
+        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
+        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
+        XCTAssertEqual(info?.exportFormat, true)
+        XCTAssertEqual(info?.sharedHistory, false)
+    }
+    
+    func test_roomKeyInfo_isUnsafeIfFromUnverifiedDevice() {
+        storeDevice(userId: "Alice", trusted: false, identityKey: "AliceSender")
+        let event = MXEvent.forwardedRoomKeyFixture(
+            senderKey: "AliceSender"
+        )
+        
+        let key = factory.roomKey(for: event)
+        
+        let info = key?.info
+        XCTAssertNotNil(key)
+        XCTAssertEqual(key?.type, .unsafe)
+        XCTAssertEqual(info?.algorithm, "megolm")
+        XCTAssertEqual(info?.sessionId, "session1")
+        XCTAssertEqual(info?.sessionKey, "<key>")
+        XCTAssertEqual(info?.roomId, "!123:matrix.org")
+        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
+        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
+        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
+        XCTAssertEqual(info?.exportFormat, true)
+        XCTAssertEqual(info?.sharedHistory, false)
+    }
+    
+    func test_roomKeyInfo_createFromForwardedRoomKeyEvent() {
+        storeDevice(userId: "Alice", trusted: true, identityKey: "AliceSender")
+        let event = MXEvent.forwardedRoomKeyFixture(
+            senderKey: "AliceSender"
+        )
+        
+        let key = factory.roomKey(for: event)
+        
+        let info = key?.info
+        XCTAssertNotNil(key)
+        XCTAssertEqual(key?.type, .safe)
+        XCTAssertEqual(info?.algorithm, "megolm")
+        XCTAssertEqual(info?.sessionId, "session1")
+        XCTAssertEqual(info?.sessionKey, "<key>")
+        XCTAssertEqual(info?.roomId, "!123:matrix.org")
+        XCTAssertEqual(info?.senderKey, "<initial_sender_key>")
+        XCTAssertEqual(info?.forwardingKeyChain, ["AliceSender"])
+        XCTAssertEqual(info?.keysClaimed as? NSDictionary, ["ed25519": "<claimed_key>"])
+        XCTAssertEqual(info?.exportFormat, true)
+        XCTAssertEqual(info?.sharedHistory, false)
+    }
+    
+    // MARK: - Helpers
+    
+    func storeOutgoingKeyRequest(
+        requestId: String = "1",
+        algorithm: String = "megolm",
+        roomId: String = "!123:matrix.org",
+        sessionId: String = "session1",
+        senderKey: String = "<initial_sender_key>"
+    ) {
+        let request = MXOutgoingRoomKeyRequest()
+        request.requestId = requestId
+        
+        request.requestBody = [
+            "room_id": roomId,
+            "algorithm": algorithm,
+            "sender_key": senderKey,
+            "session_id": sessionId
+        ]
+        store.store(request)
+    }
+    
+    func storeDevice(userId: String, trusted: Bool, identityKey: String) {
+        let trust = MXDeviceTrustLevel(
+            localVerificationStatus: trusted ? .verified : .unverified,
+            crossSigningVerified: false
+        )
+        let device = MXDeviceInfo(fromJSON: [
+            "user_id": userId,
+            "device_id": "ABC",
+            "keys": ["curve25519:ABC": identityKey]
+        ])!
+        device.setValue(trust, forKey: "trustLevel")
+        store.storeDevice(forUser: userId, device: device)
+    }
+}

programming_language: Swift
filename: MXEventFixtures.swift
change_type: ModificationType.MODIFY
diff: @@ -34,4 +34,72 @@ extension MXEvent {
             ]
         ])!
     }
+    
+    static func roomKeyFixture(
+        algorithm: String = "megolm",
+        roomId: String = "!123:matrix.org",
+        sessionId: String = "session1",
+        sessionKey: String = "<key>",
+        senderKey: String = "<sender_key>",
+        claimedKey: String = "<claimed_key>",
+        sharedHistory: Bool? = nil
+    ) -> MXEvent {
+        var content: [String: Any] = [
+            "type": kMXEventTypeStringRoomKey,
+            "room_id": roomId,
+            "session_id": sessionId,
+            "session_key": sessionKey,
+            "algorithm": algorithm
+        ]
+        
+        if let sharedHistory = sharedHistory {
+            content["org.matrix.msc3061.shared_history"] = sharedHistory
+        }
+        
+        let result = MXEventDecryptionResult()
+        result.senderCurve25519Key = senderKey
+        result.claimedEd25519Key = claimedKey
+        result.clearEvent = [
+            "type": kMXEventTypeStringRoomKey,
+            "content": content
+        ]
+        
+        let event = MXEvent(fromJSON: [:])!
+        event.setClearData(result)
+        return event
+    }
+    
+    static func forwardedRoomKeyFixture(
+        algorithm: String = "megolm",
+        roomId: String = "!123:matrix.org",
+        sessionId: String = "session1",
+        sessionKey: String = "<key>",
+        senderKey: String = "<sender_key>",
+        initialSenderKey: String = "<initial_sender_key>",
+        claimedKey: String = "<claimed_key>",
+        sharedHistory: Bool = false
+    ) -> MXEvent {
+        let content: [String: Any] = [
+            "type": kMXEventTypeStringRoomKey,
+            "room_id": roomId,
+            "session_id": sessionId,
+            "session_key": sessionKey,
+            "algorithm": algorithm,
+            "sender_key": initialSenderKey,
+            "sender_claimed_ed25519_key": claimedKey,
+            kMXSharedHistoryKeyName: sharedHistory
+        ]
+        
+        let result = MXEventDecryptionResult()
+        result.senderCurve25519Key = senderKey
+        result.claimedEd25519Key = claimedKey
+        result.clearEvent = [
+            "type": kMXEventTypeStringRoomForwardedKey,
+            "content": content
+        ]
+        
+        let event = MXEvent(fromJSON: [:])!
+        event.setClearData(result)
+        return event
+    }
 }

programming_language: Swift
filename: MXCryptoSecretShareTests.m
change_type: ModificationType.MODIFY
diff: @@ -82,7 +82,7 @@
  
  - Alice has a secret on her 1st device
  - Alice logs in on a new device
- - Alice trusts the new device
+ - Alice trusts the new device and vice versa
  - Alice requests the secret from the new device
  -> She gets the secret
  */
@@ -101,8 +101,9 @@
             
             MXCredentials *newAlice = newAliceSession.matrixRestClient.credentials;
             
-            // - Alice trusts the new device
+            // - Alice trusts the new device and vice versa
             [aliceSession.crypto setDeviceVerification:MXDeviceVerified forDevice:newAlice.deviceId ofUser:newAlice.userId success:nil failure:nil];
+            [newAliceSession.crypto setDeviceVerification:MXDeviceVerified forDevice:aliceSession.myDeviceId ofUser:aliceSession.myUserId success:nil failure:nil];
             
             // - Alice requests the secret from the new device
             [newAliceSession.crypto.secretShareManager requestSecret:secretId toDeviceIds:nil success:^(NSString * _Nonnull requestId) {

programming_language: Objective-C
filename: MXCryptoTests.m
change_type: ModificationType.MODIFY
diff: @@ -31,6 +31,7 @@
 #import "MXSendReplyEventDefaultStringLocalizer.h"
 #import "MXOutboundSessionInfo.h"
 #import <OLMKit/OLMKit.h>
+#import "MXLRUCache.h"
 
 #import "MXKey.h"
 
@@ -2081,6 +2082,8 @@
 
                 id<MXCryptoStore> bobCryptoStore = (id<MXCryptoStore>)[bobSession.crypto.olmDevice valueForKey:@"store"];
                 [bobCryptoStore removeInboundGroupSessionWithId:sessionId andSenderKey:toDeviceEvent.senderKey];
+                MXLRUCache *cache = [bobSession.crypto.olmDevice valueForKey:@"inboundGroupSessionCache"];
+                [cache clear];
 
                 // So that we cannot decrypt it anymore right now
                 [event setClearData:nil];

programming_language: Objective-C
filename: MXToolsUnitTests.m
change_type: ModificationType.MODIFY
diff: @@ -18,6 +18,7 @@
 
 #import "MXTools.h"
 #import "MatrixSDKTestsSwiftHeader.h"
+#import "MatrixSDKSwiftHeader.h"
 
 @interface MXToolsUnitTests : XCTestCase
 
@@ -129,4 +130,54 @@
     XCTAssertNotEqualObjects(url, [MXTools urlStringWithBase:currentResult queryParameters:@[parameter]]);
 }
 
+#pragma mark - Supported To-Device events
+
+- (void)testSupportedToDeviceEvents
+{
+    MXEvent *event1 = [MXEvent modelFromJSON:@{
+        @"type": @"m.room.encrypted",
+        @"content": @{
+            @"algorithm": kMXCryptoOlmAlgorithm
+        }
+    }];
+    XCTAssertTrue([MXTools isSupportedToDeviceEvent:event1]);
+    
+    MXEvent *event2 = [MXEvent modelFromJSON:@{
+        @"type": @"m.room.message",
+    }];
+    XCTAssertTrue([MXTools isSupportedToDeviceEvent:event2]);
+    
+    MXEvent *event3 = [MXEvent modelFromJSON:@{
+        @"type": @"random",
+    }];
+    XCTAssertTrue([MXTools isSupportedToDeviceEvent:event3]);
+}
+
+- (void)testUnsupportedToDeviceEvents
+{
+    MXEvent *event1 = [MXEvent modelFromJSON:@{
+        @"type": @"m.room.encrypted",
+        @"content": @{
+            @"algorithm": kMXCryptoMegolmAlgorithm
+        }
+    }];
+    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event1]);
+    
+    MXEvent *event2 = [MXEvent modelFromJSON:@{
+        @"type": @"m.room_key",
+    }];
+    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event2]);
+    
+    MXEvent *event3 = [MXEvent modelFromJSON:@{
+        @"type": @"m.forwarded_room_key",
+    }];
+    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event3]);
+    
+    MXEvent *event4 = [MXEvent modelFromJSON:@{
+        @"type": @"m.secret.send",
+    }];
+    XCTAssertFalse([MXTools isSupportedToDeviceEvent:event4]);
+}
+
+
 @end

programming_language: Objective-C
filename: UnitTests.xctestplan
change_type: ModificationType.MODIFY
diff: @@ -52,10 +52,12 @@
         "MXEventsByTypesEnumeratorOnArrayTests",
         "MXEventsEnumeratorOnArrayTests",
         "MXFilterUnitTests",
+        "MXForwardedRoomKeyEventContentUnitTests",
         "MXHTTPAdditionalHeadersUnitTests",
         "MXJSONModelUnitTests",
         "MXKeyBackupUnitTests",
         "MXKeyProviderUnitTests",
+        "MXKeyVerificationRequestV2UnitTests",
         "MXMediaScanStoreUnitTests",
         "MXMegolmDecryptionUnitTests",
         "MXMegolmExportEncryptionUnitTests",
@@ -67,7 +69,10 @@
         "MXQRCodeDataUnitTests",
         "MXReplyEventParserUnitTests",
         "MXResponseUnitTests",
+        "MXRoomKeyEventContentUnitTests",
+        "MXRoomKeyInfoFactoryUnitTests",
         "MXRoomStateUnitTests",
+        "MXSASTransactionV2UnitTests",
         "MXSharedHistoryKeyManagerUnitTests",
         "MXStoreRoomListDataManagerUnitTests",
         "MXSyncResponseUnitTests",
@@ -75,7 +80,8 @@
         "MXThreadEventTimelineUnitTests",
         "MXThreadingServiceUnitTests",
         "MXToolsUnitTests",
-        "MXTrustLevelSourceUnitTests"
+        "MXTrustLevelSourceUnitTests",
+        "MXUnrequestedForwardedRoomKeyManagerUnitTests",
       ],
       "target" : {
         "containerPath" : "container:MatrixSDK.xcodeproj",

programming_language: Swift
filename: UnitTestsWithSanitizers.xctestplan
change_type: ModificationType.MODIFY
diff: @@ -60,10 +60,12 @@
         "MXEventReferenceUnitTests",
         "MXEventScanStoreUnitTests",
         "MXFilterUnitTests",
+        "MXForwardedRoomKeyEventContentUnitTests",
         "MXHTTPAdditionalHeadersUnitTests",
         "MXJSONModelUnitTests",
         "MXKeyBackupUnitTests",
         "MXKeyProviderUnitTests",
+        "MXKeyVerificationRequestV2UnitTests",
         "MXMediaScanStoreUnitTests",
         "MXMegolmDecryptionUnitTests",
         "MXMegolmExportEncryptionUnitTests",
@@ -75,7 +77,10 @@
         "MXQRCodeDataUnitTests",
         "MXReplyEventParserUnitTests",
         "MXResponseUnitTests",
+        "MXRoomKeyEventContentUnitTests",
+        "MXRoomKeyInfoFactoryUnitTests",
         "MXRoomStateUnitTests",
+        "MXSASTransactionV2UnitTests",
         "MXSharedHistoryKeyManagerUnitTests",
         "MXStoreRoomListDataManagerUnitTests",
         "MXSyncResponseUnitTests",
@@ -83,7 +88,8 @@
         "MXThreadEventTimelineUnitTests",
         "MXThreadingServiceUnitTests",
         "MXToolsUnitTests",
-        "MXTrustLevelSourceUnitTests"
+        "MXTrustLevelSourceUnitTests",
+        "MXUnrequestedForwardedRoomKeyManagerUnitTests",
       ],
       "target" : {
         "containerPath" : "container:MatrixSDK.xcodeproj",

programming_language: Swift