CVE-2022-39257: Improper Authentication (CWE-287)
value: 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.
CWE description: When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
CVSS v3 base score: 7.5
Published: 2022-09-28T21:15Z
Repository: https://github.com/matrix-org/matrix-ios-sdk
Language: Swift
Files changed: CHANGES.md, MatrixSDK.xcodeproj/project.pbxproj, MatrixSDK/Background/MXBackgroundSyncService.swift, MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift, MatrixSDK/Crypto/Algorithms/MXDecrypting.h, MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m, MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m, MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m, MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h, MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m, MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m, MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.h, MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.m, MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.h, MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.m, MatrixSDK/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManager.swift, MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m, MatrixSDK/Crypto/MXCrypto.m, MatrixSDK/Crypto/MXCrypto_Private.h, MatrixSDK/Crypto/MXOlmDevice.h, MatrixSDK/Crypto/MXOlmDevice.m, MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfo.swift, MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfoFactory.swift, MatrixSDK/Crypto/RoomKeys/MXRoomKeyResult.swift, MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransaction.m, MatrixSDK/MXSession.m, MatrixSDK/MatrixSDK.h, MatrixSDK/Utils/Categories/Dictionary.swift, MatrixSDK/Utils/MXDateProvider.swift, MatrixSDK/Utils/MXLRUCache.m, MatrixSDK/Utils/MXTools.swift, MatrixSDKTests/Crypto/Algorithms/Megolm/MXMegolmDecryptionUnitTests.swift, MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift, MatrixSDKTests/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContentUnitTests.swift, MatrixSDKTests/Crypto/KeySharing/Data/MXRoomKeyEventContentUnitTests.swift, MatrixSDKTests/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManagerUnitTests.swift, MatrixSDKTests/Crypto/MXOlmDeviceUnitTests.swift, MatrixSDKTests/Crypto/RoomKeys/MXRoomKeyInfoFactoryUnitTests.swift, MatrixSDKTests/JSONModels/MXEventFixtures.swift, MatrixSDKTests/MXCryptoSecretShareTests.m, MatrixSDKTests/MXCryptoTests.m, MatrixSDKTests/MXToolsUnitTests.m, MatrixSDKTests/TestPlans/UnitTests.xctestplan, MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan
Commit date: 2022-09-28 15:25:36 +0100
Commit message: Resolve multiple CVEs

CVE-2022-39255
CVE-2022-39257

Diff: diff --git a/CHANGES.md b/CHANGES.md
index 4c9385e3..d1849977 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,12 @@
+## 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
 
 - MXKeyBackup: Add support for symmetric key backups. ([#1542](https://github.com/matrix-org/matrix-ios-sdk/pull/1542))
diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj
index 188a5c8c..e8981b48 100644
--- a/MatrixSDK.xcodeproj/project.pbxproj
+++ b/MatrixSDK.xcodeproj/project.pbxproj
@@ -1799,10 +1799,18 @@
 		ECF29BD4264194BB0053E6D6 /* MXCallAssertedIdentityEventContent.m in Sources */ = {isa = PBXBuildFile; fileRef = ECF29BD2264194BB0053E6D6 /* MXCallAssertedIdentityEventContent.m */; };
 		ECF29BDE264195320053E6D6 /* MXAssertedIdentityModel.h in Headers */ = {isa = PBXBuildFile; fileRef = ECF29BDD264195320053E6D6 /* MXAssertedIdentityModel.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		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 */; };
 		ED21F68628104DA2002FF83D /* MXMegolmEncryptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED21F68428104DA2002FF83D /* MXMegolmEncryptionTests.swift */; };
 		ED2DD114286C450600F06731 /* MXCryptoMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD111286C450600F06731 /* MXCryptoMachine.swift */; };
@@ -1844,10 +1852,32 @@
 		ED5C754728B3E80300D24E85 /* MXLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = ED5C753A28B3E80300D24E85 /* MXLogger.m */; };
 		ED5C754828B3E80300D24E85 /* MXLogObjcWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = ED5C753B28B3E80300D24E85 /* MXLogObjcWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		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 */; };
 		ED7019E02886C26000FC31B9 /* MXDeviceInfoSourceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D2B2885A80B00F897E7 /* MXDeviceInfoSourceUnitTests.swift */; };
 		ED7019E12886C26D00FC31B9 /* MXCryptoRequestsUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED2DD11B286C4F3E00F06731 /* MXCryptoRequestsUnitTests.swift */; };
@@ -2908,10 +2938,14 @@
 		ECE3DFA7270CF69500FB4C96 /* MockRoomSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRoomSummary.swift; sourceTree = "<group>"; };
 		ECF29BCB264194AE0053E6D6 /* MXCallAssertedIdentityEventContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCallAssertedIdentityEventContent.h; sourceTree = "<group>"; };
 		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>"; };
 		ED2DD112286C450600F06731 /* MXEventDecryptionResult+DecryptedEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MXEventDecryptionResult+DecryptedEvent.swift"; sourceTree = "<group>"; };
 		ED2DD113286C450600F06731 /* MXCryptoRequests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoRequests.swift; sourceTree = "<group>"; };
@@ -2933,10 +2967,21 @@
 		ED5C753828B3E80300D24E85 /* MXAnalyticsDestination.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXAnalyticsDestination.swift; sourceTree = "<group>"; };
 		ED5C753928B3E80300D24E85 /* MXLogObjcWrapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXLogObjcWrapper.m; sourceTree = "<group>"; };
 		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>"; };
 		ED7019EF2886CA6C00FC31B9 /* MXKeyVerificationRequestV2UnitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXKeyVerificationRequestV2UnitTests.swift; sourceTree = "<group>"; };
 		ED7019F02886CA6C00FC31B9 /* VerificationRequest+Stub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "VerificationRequest+Stub.swift"; sourceTree = "<group>"; };
@@ -3227,10 +3272,11 @@
 				32A9E8231EF4026E0081358A /* MXUIKitBackgroundModeHandler.m */,
 				B17B2BDA2369FC81009D6650 /* MXUIKitBackgroundTask.h */,
 				B17B2BDB2369FC81009D6650 /* MXUIKitBackgroundTask.m */,
 				EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */,
 				ED1AE9292881AC7100D3432A /* MXWarnings.h */,
+				ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */,
 			);
 			path = Utils;
 			sourceTree = "<group>";
 		};
 		32114A831A262CE000FF2EC4 /* Store */ = {
@@ -3286,10 +3332,11 @@
 				32CEEF3F23AD2A340039BA98 /* CrossSigning */,
 				ED8F1D272885A7B600F897E7 /* Devices */,
 				32261B8723C749FB0018F1E2 /* Trust */,
 				32A1513B1DAF768D00400192 /* Data */,
 				3AC1379326432ED500EE1E74 /* Dehydration */,
+				ED6DAC0428C771D500ECDCB6 /* RoomKeys */,
 				32BBAE642178E99100D85F46 /* KeyBackup */,
 				32FA10B21FA1C28100E54233 /* KeySharing */,
 				3A108A382580E979005EEBE9 /* KeyProvider */,
 				32C78B64256CFC4D008130B1 /* Migration */,
 				32F00AB82488E14300131741 /* Recovery */,
@@ -4316,10 +4363,11 @@
 				32AF927A240EA0190008A0FD /* Secret */,
 				32FA10C31FA1C9F700E54233 /* Data */,
 				32FA10BF1FA1C9EE00E54233 /* MXOutgoingRoomKeyRequestManager.h */,
 				32FA10C01FA1C9EE00E54233 /* MXOutgoingRoomKeyRequestManager.m */,
 				32A30B161FB4813400C8309E /* MXIncomingRoomKeyRequestManager.h */,
+				ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */,
 				32A30B171FB4813400C8309E /* MXIncomingRoomKeyRequestManager.m */,
 				ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */,
 				EDBCF335281A8AB900ED5044 /* MXSharedHistoryKeyService.h */,
 				EDBCF338281A8D3D00ED5044 /* MXSharedHistoryKeyService.m */,
 			);
@@ -4327,10 +4375,14 @@
 			sourceTree = "<group>";
 		};
 		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 */,
 				32F945F11FAB83D800622468 /* MXIncomingRoomKeyRequestCancellation.m */,
 				32FA10C81FA1C9F700E54233 /* MXOutgoingRoomKeyRequest.h */,
@@ -5125,10 +5177,11 @@
 			isa = PBXGroup;
 			children = (
 				ED8F1D1428857FD300F897E7 /* CrossSigning */,
 				ED8F1D2E2885AAEB00F897E7 /* Trust */,
 				ED8F1D292885A7DF00F897E7 /* Devices */,
+				ED6DAC0F28C7889A00ECDCB6 /* RoomKeys */,
 				ED44F01628180F1300452A5D /* KeySharing */,
 				ED35652A281150230002BF6A /* Data */,
 				ED21F67B28104BA1002FF83D /* Algorithms */,
 				ED2DD11A286C4F3100F06731 /* CryptoMachine */,
 				ED7019ED2886CA6C00FC31B9 /* Verification */,
@@ -5176,20 +5229,23 @@
 			sourceTree = "<group>";
 		};
 		ED35652A281150230002BF6A /* Data */ = {
 			isa = PBXGroup;
 			children = (
+				ED6DAC1328C78D3700ECDCB6 /* Store */,
 				ED35652B281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift */,
 				ED35652E281153480002BF6A /* MXMegolmSessionDataUnitTests.swift */,
 			);
 			path = Data;
 			sourceTree = "<group>";
 		};
 		ED44F01628180F1300452A5D /* KeySharing */ = {
 			isa = PBXGroup;
 			children = (
+				ED6DAC0528C77E0600ECDCB6 /* Data */,
 				ED44F01728180F1C00452A5D /* MXSharedHistoryKeyManagerUnitTests.swift */,
+				ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */,
 			);
 			path = KeySharing;
 			sourceTree = "<group>";
 		};
 		ED5C753428B3E80300D24E85 /* Logs */ = {
@@ -5204,10 +5260,45 @@
 				ED5C753928B3E80300D24E85 /* MXLogObjcWrapper.m */,
 			);
 			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 = (
 				ED7019EE2886CA6C00FC31B9 /* Requests */,
 				ED7019F12886CA6C00FC31B9 /* Transactions */,
@@ -5363,10 +5454,11 @@
 		};
 		F08B8D591E014711006171A8 /* Categories */ = {
 			isa = PBXGroup;
 			children = (
 				3259D0072603705300C365DB /* Array.swift */,
+				ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */,
 				F08B8D5A1E014711006171A8 /* NSData+MatrixSDK.h */,
 				F08B8D5B1E014711006171A8 /* NSData+MatrixSDK.m */,
 				3259D02226037A7200C365DB /* NSDictionary.swift */,
 				3259D02126037A7200C365DB /* NSArray.swift */,
 				321CFDEC225264C4004D31DF /* NSArray+MatrixSDK.h */,
@@ -5702,10 +5794,11 @@
 				18C26C53273C16C800805154 /* MXEventContentPollStart.h in Headers */,
 				3293C700214BBA4F009B3DDB /* MXPeekingRoomSummary.h in Headers */,
 				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 */,
 				321CFDEA22525DEE004D31DF /* MXIncomingSASTransaction.h in Headers */,
 				32F945F61FAB83D900622468 /* MXIncomingRoomKeyRequest.h in Headers */,
@@ -5729,10 +5822,11 @@
 				B19A30CE24042F0800FB6F35 /* MXSelfVerifyingMasterKeyTrustedQRCodeData.h in Headers */,
 				EC0B9436271DB0D600B4D440 /* MXMemoryRoomSummaryStore.h in Headers */,
 				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 */,
 				320DFDE219DD99B60068622A /* MXError.h in Headers */,
 				B11BD45922CB58850064D8B0 /* MXReplyEventFormattedBodyParts.h in Headers */,
@@ -6006,10 +6100,11 @@
 				B14EF31D2397E90400758AF0 /* MXEventDecryptionResult.h in Headers */,
 				B14EF31F2397E90400758AF0 /* MXPusherData.h in Headers */,
 				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 */,
 				B14EF3232397E90400758AF0 /* MXRoomState.h in Headers */,
 				B135066227E9CB6400BD3276 /* MXBeaconInfo.h in Headers */,
@@ -6051,10 +6146,11 @@
 				B14EF3362397E90400758AF0 /* MXEventScanStore.h in Headers */,
 				3287165023C4C12200D720CA /* MXKeyVerificationManager_Private.h in Headers */,
 				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 */,
 				B14EF3392397E90400758AF0 /* MXRealmReactionCount.h in Headers */,
 				B14EF33A2397E90400758AF0 /* MXLoginPolicyData.h in Headers */,
@@ -6468,10 +6564,11 @@
 				ECBF658126DE2A8500AA3A99 /* MXMemoryRoomOutgoingMessagesStore.m in Sources */,
 				32F00ABD2488E1CD00131741 /* MXRecoveryService.m in Sources */,
 				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 */,
 				3291DC8523DF52E20009732F /* MXRoomCreationParameters.m in Sources */,
 				32C235731F827F3800E38FC5 /* MXRoomOperation.m in Sources */,
@@ -6485,10 +6582,11 @@
 				B16C2455283AB0DE00F5D1FE /* MXRealmBeaconMapper.swift in Sources */,
 				3251D41F25AF01D7001E6E77 /* MXUIKitApplicationStateService.swift in Sources */,
 				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 */,
 				EC0B941327184E8A00B4D440 /* MXRoomMembersCountMO.swift in Sources */,
 				323360701A403A0D0071A488 /* MXFileStore.m in Sources */,
@@ -6596,10 +6694,11 @@
 				325380E9228DAD4A00ADDEFA /* MXAggregatedReactions.m in Sources */,
 				EC60EDD2265CFECC00B39A4E /* MXRoomSyncSummary.m in Sources */,
 				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 */,
 				C6F935881E5B3BE600FC34BF /* MX3PID.swift in Sources */,
 				B16C2458283AB10B00F5D1FE /* MXRealmBeaconInfoSummary.swift in Sources */,
@@ -6638,10 +6737,11 @@
 				3283F7791EAF30F700C1688C /* MXBugReportRestClient.m in Sources */,
 				32B0E33B23A2989A0054FF1A /* MXEventReferenceChunk.m in Sources */,
 				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 */,
 				EC60EE08265CFFF400B39A4E /* MXGroupSyncProfile.m in Sources */,
 				B1432B53282AB29A00737CA6 /* MXBeaconInfoSummaryPerRoomListener.swift in Sources */,
@@ -6651,10 +6751,11 @@
 				C63E78B01F26588000AC692F /* MXRoomPowerLevels.swift in Sources */,
 				EC0B943F271DB68F00B4D440 /* MXVoidRoomSummaryStore.m in Sources */,
 				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 */,
 				EDF1B6902876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */,
 				EC8A539525B1BC77004E0802 /* MXUserModel.m in Sources */,
@@ -6677,10 +6778,11 @@
 				EC383BAC254030DF002FBBE6 /* MXBackgroundStore.swift in Sources */,
 				EC0B942A2718F55C00B4D440 /* MXCoreDataRoomListDataManager.swift in Sources */,
 				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 */,
 				EC1165C427107E330089FA56 /* MXRoomListDataFilterOptions.swift in Sources */,
 				3A108A8025810C96005EEBE9 /* MXKeyData.m in Sources */,
@@ -6752,10 +6854,11 @@
 				32133016228AF4EF0070BA9B /* MXRealmAggregationsStore.m in Sources */,
 				B19A30A62404257700FB6F35 /* MXQRCodeKeyVerificationStart.m in Sources */,
 				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 */,
 				B1136964230AC9D900E2B2FA /* MXIdentityServerRestClient.m in Sources */,
 				EDD578E32881C37C006739DD /* MXTrustLevelSource.swift in Sources */,
@@ -6775,10 +6878,11 @@
 				EC8A538D25B1BC77004E0802 /* MXCallSessionDescription.m in Sources */,
 				F08B8D5D1E014711006171A8 /* NSData+MatrixSDK.m in Sources */,
 				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 */,
 				EC5C560C2798CEA00014CBE9 /* NSDictionary+MutableDeepCopy.m in Sources */,
 				ECD2897926E8EE9300F268CF /* MXRoomListDataSortOptions.swift in Sources */,
@@ -6889,30 +6993,33 @@
 				323C5A081A70E53500FB0549 /* MXToolsUnitTests.m in Sources */,
 				3281E89E19E299C000976E1A /* MXErrorUnitTests.m in Sources */,
 				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 */,
 				EC0B944627206D0B00B4D440 /* MXCoreDataRoomListDataManagerUnitTests.swift in Sources */,
 				32CEEF3D23AD134A0039BA98 /* MXCrossSigningTests.m in Sources */,
 				326D1EF51BFC79300030947B /* MXPushRuleUnitTests.m in Sources */,
 				EC1165CE27107F3E0089FA56 /* MXRoomListDataManagerTests.swift in Sources */,
 				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 */,
 				18C26C4A273C0B3A00805154 /* PollAggregator.swift in Sources */,
 				3295719A1B024D2B00ABB3BA /* MXMockCallStackCall.m in Sources */,
 				324DD2BB246C3ADE00377005 /* MXCryptoSecretStorageTests.m in Sources */,
 				B19A30D82404335D00FB6F35 /* MXQRCodeDataUnitTests.m in Sources */,
 				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 */,
 				B11BD45C22CB8ABC0064D8B0 /* MXReplyEventParserUnitTests.m in Sources */,
 				ECAE7AEC24ED75F1002FA813 /* MXHTTPAdditionalHeadersUnitTests.m in Sources */,
@@ -6923,10 +7030,11 @@
 				321809B919EEBF3000377451 /* MXEventTests.m in Sources */,
 				B1B44319283D00CA00BB26F4 /* MXMegolmDecryptionUnitTests.swift in Sources */,
 				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 */,
 				C61A4AF41E5DD88400442158 /* Dummy.swift in Sources */,
 				32935F61216FA49D00A1BC24 /* MXCurve25519KeyBackupTests.m in Sources */,
@@ -6950,10 +7058,11 @@
 				329571931B0240CE00ABB3BA /* MXVoIPTests.m in Sources */,
 				ED8F1D322885AC5700F897E7 /* Device+Stub.swift in Sources */,
 				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 */,
 				32C78BA7256D227D008130B1 /* MXCryptoMigrationTests.m in Sources */,
 				ED7019F92886CA6C00FC31B9 /* Sas+Stub.swift in Sources */,
@@ -7067,10 +7176,11 @@
 				ECBF658226DE2A8500AA3A99 /* MXMemoryRoomOutgoingMessagesStore.m in Sources */,
 				324DD2AF246AEB7B00377005 /* MXSecretStoragePassphrase.m in Sources */,
 				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 */,
 				B14EF1F22397E90400758AF0 /* MXIdentityService.swift in Sources */,
 				B14EF1F32397E90400758AF0 /* MXServerNotices.m in Sources */,
@@ -7084,10 +7194,11 @@
 				B16C2456283AB0DE00F5D1FE /* MXRealmBeaconMapper.swift in Sources */,
 				3259D02426037A7200C365DB /* NSArray.swift in Sources */,
 				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 */,
 				EC0B941427184E8A00B4D440 /* MXRoomMembersCountMO.swift in Sources */,
 				B14EF1F92397E90400758AF0 /* MXReactionRelation.m in Sources */,
@@ -7195,10 +7306,11 @@
 				B14EF2282397E90400758AF0 /* MXNoStore.m in Sources */,
 				ECF29BD4264194BB0053E6D6 /* MXCallAssertedIdentityEventContent.m in Sources */,
 				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 */,
 				ECD2899626EB3B4200F268CF /* MXStoreRoomListDataFetcher.swift in Sources */,
 				B16C2459283AB10B00F5D1FE /* MXRealmBeaconInfoSummary.swift in Sources */,
@@ -7237,10 +7349,11 @@
 				B14EF23A2397E90400758AF0 /* MXHTTPOperation.m in Sources */,
 				A780625127B2CE74005780C0 /* FileManager+AppGroupContainer.swift in Sources */,
 				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 */,
 				B14EF23F2397E90400758AF0 /* MXEncryptedContentFile.m in Sources */,
 				B1432B54282AB29A00737CA6 /* MXBeaconInfoSummaryPerRoomListener.swift in Sources */,
@@ -7250,10 +7363,11 @@
 				EC0B9440271DB68F00B4D440 /* MXVoidRoomSummaryStore.m in Sources */,
 				B14EF2402397E90400758AF0 /* MXSessionEventListener.m in Sources */,
 				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 */,
 				EDF1B6912876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */,
 				B14EF2422397E90400758AF0 /* MXDeviceInfo.m in Sources */,
@@ -7276,10 +7390,11 @@
 				EC0B942B2718F55C00B4D440 /* MXCoreDataRoomListDataManager.swift in Sources */,
 				324DD2B9246C21C700377005 /* MXSecretStorageKeyCreationInfo.m in Sources */,
 				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 */,
 				B14EF24C2397E90400758AF0 /* MXCredentials.m in Sources */,
 				EC116590270F3ABF0089FA56 /* RLMRealm+MatrixSDK.m in Sources */,
@@ -7351,10 +7466,11 @@
 				ED5C754128B3E80300D24E85 /* MXLog.swift in Sources */,
 				B14EF26A2397E90400758AF0 /* MXReactionCountChangeListener.m in Sources */,
 				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 */,
 				324676ED25C15F4600EA855B /* MXMemory.swift in Sources */,
 				EC60ED6A265CFC7200B39A4E /* MXPresenceSyncResponse.m in Sources */,
@@ -7374,10 +7490,11 @@
 				EC60ED88265CFD0700B39A4E /* MXRoomsSyncResponse.m in Sources */,
 				B14EF2742397E90400758AF0 /* MXDeviceListOperationsPool.m in Sources */,
 				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 */,
 				B14EF27A2397E90400758AF0 /* MXSessionEventListener.swift in Sources */,
 				B14EF27B2397E90400758AF0 /* MXOlmSessionResult.m in Sources */,
@@ -7488,30 +7605,33 @@
 				32CEEF3E23AD134A0039BA98 /* MXCrossSigningTests.m in Sources */,
 				32EEA8402603CA140041425B /* MXRestClientExtensionsTests.m in Sources */,
 				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 */,
 				B1E09A2B2397FD6B0057C069 /* MatrixSDKTestsE2EData.m in Sources */,
 				B1E09A3C2397FD820057C069 /* MXStoreMemoryStoreTests.m in Sources */,
 				EC1165CF27107F3E0089FA56 /* MXRoomListDataManagerTests.swift in Sources */,
 				ED7019E22886C28900FC31B9 /* MXCryptoProtocolStubs.swift in Sources */,
 				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 */,
 				18C26C4B273C0B3A00805154 /* PollAggregator.swift in Sources */,
 				324DD2BC246C3ADE00377005 /* MXCryptoSecretStorageTests.m in Sources */,
 				B19A30D92404335D00FB6F35 /* MXQRCodeDataUnitTests.m in Sources */,
 				B1E09A212397FCE90057C069 /* DirectRoomTests.m in Sources */,
 				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 */,
 				B1E09A292397FD080057C069 /* MXLoggerUnitTests.m in Sources */,
 				B1E09A492398028D0057C069 /* MXSelfSignedHomeserverTests.m in Sources */,
@@ -7522,10 +7642,11 @@
 				B1E09A1B2397FCE90057C069 /* MXAggregatedReactionTests.m in Sources */,
 				B1E09A3F2397FD820057C069 /* MXNotificationCenterTests.m in Sources */,
 				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 */,
 				B1E09A272397FD010057C069 /* MXMockCallStack.m in Sources */,
 				B14EECEF2578FE3F00448735 /* MXAuthenticationSessionUnitTests.swift in Sources */,
@@ -7549,10 +7670,11 @@
 				18937E7D273A5AE500902626 /* MXPollRelationTests.m in Sources */,
 				B1E09A352397FD7D0057C069 /* MXEventTests.m in Sources */,
 				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 */,
 				B1E09A322397FD750057C069 /* MXRoomTests.m in Sources */,
 				ED7019FA2886CA6C00FC31B9 /* Sas+Stub.swift in Sources */,
diff --git a/MatrixSDK/Background/MXBackgroundSyncService.swift b/MatrixSDK/Background/MXBackgroundSyncService.swift
index 6d377261..2a7c9397 100644
--- a/MatrixSDK/Background/MXBackgroundSyncService.swift
+++ b/MatrixSDK/Background/MXBackgroundSyncService.swift
@@ -562,74 +562,54 @@ public enum MXBackgroundSyncServiceError: Error {
         
         MXLog.debug("[MXBackgroundSyncService] handleSyncResponse: Next sync token: \(syncResponse.nextBatch)")
     }
     
     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)
             } catch let error {
                 MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: Could not decrypt to-device event: \(error)")
                 return
             }
         }
         
-        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() {
         var outdatedStore = false
         
diff --git a/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift b/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift
index 856ba651..dde0e04a 100644
--- a/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift
+++ b/MatrixSDK/Contrib/Swift/JSONModels/MXEvent.swift
@@ -71,10 +71,13 @@ public enum MXEventType: Equatable, Hashable {
     case keyVerificationAccept
     case keyVerificationKey
     case keyVerificationMac
     case keyVerificationCancel
     case keyVerificationDone
+    case secretRequest
+    case secretSend
+    case secretStorageDefaultKey
     case taggedEvents
     case spaceChild
     case spaceOrder
     
     case pollStart
@@ -130,10 +133,13 @@ public enum MXEventType: Equatable, Hashable {
         case .keyVerificationAccept: return kMXEventTypeStringKeyVerificationAccept
         case .keyVerificationKey: return kMXEventTypeStringKeyVerificationKey
         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
             
         case .pollStart: return kMXEventTypeStringPollStartMSC3381
@@ -149,11 +155,11 @@ public enum MXEventType: Equatable, Hashable {
         case .custom(let string): return string
         }
     }
     
     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
         } else {
             if identifier == kMXEventTypeStringPollStart {
diff --git a/MatrixSDK/Crypto/Algorithms/MXDecrypting.h b/MatrixSDK/Crypto/Algorithms/MXDecrypting.h
index 58a01aa1..fed7417f 100644
--- a/MatrixSDK/Crypto/Algorithms/MXDecrypting.h
+++ b/MatrixSDK/Crypto/Algorithms/MXDecrypting.h
@@ -21,11 +21,11 @@
 #import "MXEvent.h"
 #import "MXDecryptionResult.h"
 #import "MXEventDecryptionResult.h"
 #import "MXIncomingRoomKeyRequest.h"
 
-@class MXCrypto, MXOlmInboundGroupSession;
+@class MXCrypto, MXOlmInboundGroupSession, MXRoomKeyResult;
 
 
 @protocol MXDecrypting <NSObject>
 
 /**
@@ -54,16 +54,23 @@
  @return The decryption result.
  */
 - (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.
 
  @param session the session data to import.
  */
diff --git a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m
index 5c112331..73559197 100644
--- a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m
+++ b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m
@@ -24,10 +24,11 @@
 #import "MXCryptoAlgorithms.h"
 #import "MXCrypto_Private.h"
 #import "MXTools.h"
 #import "MatrixSDKSwiftHeader.h"
 #import "MXSharedHistoryKeyService.h"
+#import "MXForwardedRoomKeyEventContent.h"
 
 @interface MXMegolmDecryption ()
 {
     // The crypto module
     MXCrypto *crypto;
@@ -39,10 +40,14 @@ @interface MXMegolmDecryption ()
     // senderKey|sessionId to timelines to list of MatrixEvents
     NSMutableDictionary<NSString* /* senderKey|sessionId */,
         NSMutableDictionary<NSString* /* timelineId */,
             NSMutableDictionary<NSString* /* eventId */, MXEvent*>*>*> *pendingEvents;
 }
+
+// Factory to create room key info
+@property (nonatomic, strong) MXRoomKeyInfoFactory *roomKeyInfoFactory;
+
 @end
 
 @implementation MXMegolmDecryption
 
 + (void)load
@@ -57,10 +62,11 @@ - (instancetype)initWithCrypto:(MXCrypto *)theCrypto
     self = [super init];
     if (self)
     {
         crypto = theCrypto;
         olmDevice = theCrypto.olmDevice;
+        _roomKeyInfoFactory = [[MXRoomKeyInfoFactory alloc] initWithMyUserId:crypto.mxSession.credentials.userId store:crypto.store];
         pendingEvents = [NSMutableDictionary dictionary];
     }
     return self;
 }
 
@@ -189,102 +195,63 @@ - (void)addEventToPendingList:(MXEvent*)event inTimeline:(NSString*)timelineId
     }
 }
 
 - (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
                                                  }];
         }
     }];
 }
 
diff --git a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m
index 19150dc5..5c1c4589 100644
--- a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m
+++ b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m
@@ -338,10 +338,11 @@ - (MXOutboundSessionInfo*)prepareNewSession
                                  keysClaimed:@{
                                                @"ed25519": crypto.olmDevice.deviceEd25519Key
                                                }
                                 exportFormat:NO
                                sharedHistory:sharedHistory
+                                   untrusted:NO
      ];
 
     [crypto.backup maybeSendKeyBackup];
 
     return [[MXOutboundSessionInfo alloc] initWithSession:session];
diff --git a/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m b/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m
index 4217078d..425a6d68 100644
--- a/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m
+++ b/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m
@@ -221,10 +221,15 @@ - (MXEventDecryptionResult *)decryptEvent:(MXEvent *)event inTimeline:(NSString
 - (void)onRoomKeyEvent:(MXEvent *)event
 {
     // No impact for olm
 }
 
+- (void)onRoomKeyInfo:(MXRoomKeyInfo *)keyInfo
+{
+    // No impact for olm
+}
+
 - (void)didImportRoomKey:(MXOlmInboundGroupSession *)session
 {
     // No impact for olm
 }
 
diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h
index 0c8d30fb..6164ea22 100644
--- a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h
+++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h
@@ -37,15 +37,10 @@ NS_ASSUME_NONNULL_BEGIN
 /**
  Signatures found in the backup version.
  */
 @property (nonatomic) NSArray<MXKeyBackupVersionTrustSignature*> *signatures;
 
-/**
- Flag indicating the backup trusted locally.
- */
-@property (nonatomic, getter=isTrustedLocally) BOOL trustedLocally;
-
 @end
 
 
 /**
  A signature in a the `MXKeyBackupVersionTrust` object.
diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m
index e286fca1..44981fe2 100644
--- a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m
+++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m
@@ -23,11 +23,10 @@ - (instancetype)init
     self = [super init];
     if (self)
     {
         _usable = NO;
         _signatures = [NSArray new];
-        _trustedLocally = NO;
     }
     return self;
 }
 
 @end
diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m
index e4cbb12f..be500312 100644
--- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m
+++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m
@@ -1121,21 +1121,10 @@ - (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBacku
     {
         MXLogDebug(@"[MXKeyBackup] trustForKeyBackupVersion: Key backup is absent or missing required data");
         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)
     {
         // XXX: is this how we're supposed to get the device id?
@@ -1194,11 +1183,10 @@ - (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBacku
         if (signature.valid && signature.device && signature.device.trustLevel.isVerified)
         {
             keyBackupVersionTrust.usable = YES;
         }
     }
-    keyBackupVersionTrust.usable = keyBackupVersionTrust.usable || keyBackupVersionTrust.isTrustedLocally;
 
     return keyBackupVersionTrust;
 }
 
 - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion
diff --git a/MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.h b/MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.h
new file mode 100644
index 00000000..f421bc9a
--- /dev/null
+++ b/MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.h
@@ -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
diff --git a/MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.m b/MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.m
new file mode 100644
index 00000000..6a3ed6a4
--- /dev/null
+++ b/MatrixSDK/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContent.m
@@ -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
diff --git a/MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.h b/MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.h
new file mode 100644
index 00000000..cfc8f8ce
--- /dev/null
+++ b/MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.h
@@ -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
diff --git a/MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.m b/MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.m
new file mode 100644
index 00000000..6d7b7676
--- /dev/null
+++ b/MatrixSDK/Crypto/KeySharing/Data/MXRoomKeyEventContent.m
@@ -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
diff --git a/MatrixSDK/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManager.swift b/MatrixSDK/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManager.swift
new file mode 100644
index 00000000..f1fea048
--- /dev/null
+++ b/MatrixSDK/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManager.swift
@@ -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
+    }
+}
diff --git a/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m b/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m
index 11732904..dba84d88 100644
--- a/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m
+++ b/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m
@@ -400,10 +400,16 @@ - (void)shareSecret:(NSString*)secret toRequest:(MXSecretShareRequest*)request
 }
 
 
 - (void)handleSecretSendEvent:(MXEvent*)event
 {
+    if (![self canAcceptSecretSendEvent:event])
+    {
+        MXLogDebug(@"[MXSecretShareManager] handleSecretSendEvent: Rejecting unacceptable secret");
+        return;
+    }
+    
     MXSecretShareSend *shareSend;
     MXJSONModelSetMXJSONModel(shareSend, MXSecretShareSend, event.content);
     if (!shareSend)
     {
         MXLogDebug(@"[MXSecretShareManager] handleSecretSendEvent: Bad content format: %@", event.JSONDictionary);
@@ -426,6 +432,31 @@ - (void)handleSecretSendEvent:(MXEvent*)event
     {
         MXLogDebug(@"[MXSecretShareManager] handleSecretSendEvent: Not valid secret. Keep request %@ on", shareSend.requestId);
     }
 }
 
+- (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
diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m
index 14fbb4eb..f8df98a6 100644
--- a/MatrixSDK/Crypto/MXCrypto.m
+++ b/MatrixSDK/Crypto/MXCrypto.m
@@ -74,11 +74,11 @@
 
 // Frequency with which to check & upload one-time keys
 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;
 
     // A map from algorithm to MXDecrypting instance, for each room
@@ -105,10 +105,13 @@ @interface MXCrypto ()
     MXOutgoingRoomKeyRequestManager *outgoingRoomKeyRequestManager;
 
     // 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;
     
     // The dedicated queue used for decryption.
@@ -432,10 +435,13 @@ - (void)close:(BOOL)deleteStore
         [self.uploadFallbackKeyOperation cancel];
         self.uploadFallbackKeyOperation = nil;
 
         [self->outgoingRoomKeyRequestManager close];
         self->outgoingRoomKeyRequestManager = nil;
+        
+        [self->unrequestedForwardedRoomKeyManager close];
+        self->outgoingRoomKeyRequestManager = nil;
 
         if (deleteStore)
         {
             [MXCryptoStoreClass deleteStoreWithCredentials:self.mxSession.matrixRestClient.credentials];
         }
@@ -981,10 +987,11 @@ - (void)onSyncCompleted:(NSString *)oldSyncToken nextSyncToken:(NSString *)nextS
         // (https://github.com/vector-im/riot-web/issues/2782).
         if (!catchingUp)
         {
             [self maybeUploadOneTimeKeys:nil failure:nil];
             [self->incomingRoomKeyRequestManager processReceivedRoomKeyRequests];
+            [self->unrequestedForwardedRoomKeyManager processUnrequestedKeys];
         }
     });
 
 #endif
 }
@@ -2042,10 +2049,13 @@ - (instancetype)initWithMatrixSession:(MXSession*)matrixSession cryptoQueue:(dis
                                          deviceId:_myDevice.deviceId
                                          cryptoQueue:[MXCrypto dispatchQueueForUser:_myDevice.userId]
                                          cryptoStore:_store];
 
         incomingRoomKeyRequestManager = [[MXIncomingRoomKeyRequestManager alloc] initWithCrypto:self];
+        
+        unrequestedForwardedRoomKeyManager = [[MXUnrequestedForwardedRoomKeyManager alloc] init];
+        unrequestedForwardedRoomKeyManager.delegate = self;
 
         _keyVerificationManager = [[MXKeyVerificationManager alloc] initWithCrypto:self];
         
         _secretStorage = [[MXSecretStorage alloc] initWithMatrixSession:_mxSession processingQueue:_cryptoQueue];
         _secretShareManager = [[MXSecretShareManager alloc] initWithCrypto:self];
@@ -2581,10 +2591,15 @@ - (void)requestRoomKey:(NSDictionary*)requestBody recipients:(NSArray<NSDictiona
 - (void)cancelRoomKeyRequest:(NSDictionary*)requestBody
 {
     [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];
     if (key)
     {
@@ -2800,20 +2815,20 @@ - (void)onCryptoEvent:(MXEvent*)event
  @param event the membership event causing the change.
  @param roomState the know state of the room when the event occurs.
  */
 - (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)
     {
         if (member.membership == MXMembershipJoin)
         {
@@ -3262,8 +3277,27 @@ - (void)markOlmSessionForUnwedgingInEvent:(MXEvent*)event
             @"error": error ?: @"unknown"
         });
     }];
 }
 
+#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
diff --git a/MatrixSDK/Crypto/MXCrypto_Private.h b/MatrixSDK/Crypto/MXCrypto_Private.h
index 80665bc2..92dd1f1b 100644
--- a/MatrixSDK/Crypto/MXCrypto_Private.h
+++ b/MatrixSDK/Crypto/MXCrypto_Private.h
@@ -31,10 +31,12 @@
 #import "MXOlmSessionResult.h"
 #import "MXKeyBackup_Private.h"
 
 #import "MXCrypto.h"
 
+@class MXRoomKeyInfo;
+
 /**
  The `MXCrypto_Private` extension exposes internal operations.
  
  These methods run on a dedicated thread and must be called with the corresponding care.
  */
@@ -222,8 +224,17 @@
 - (void)cancelRoomKeyRequest:(NSDictionary*)requestBody;
 
 // 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
diff --git a/MatrixSDK/Crypto/MXOlmDevice.h b/MatrixSDK/Crypto/MXOlmDevice.h
index d627a1b6..9fe170f5 100644
--- a/MatrixSDK/Crypto/MXOlmDevice.h
+++ b/MatrixSDK/Crypto/MXOlmDevice.h
@@ -239,11 +239,12 @@ Determine if an incoming messages is a prekey message matching an existing sessi
                         roomId:(NSString*)roomId
                      senderKey:(NSString*)senderKey
   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.
 
  @param inboundGroupSessionsData the group sessions data.
diff --git a/MatrixSDK/Crypto/MXOlmDevice.m b/MatrixSDK/Crypto/MXOlmDevice.m
index e52a6f07..24931ec5 100644
--- a/MatrixSDK/Crypto/MXOlmDevice.m
+++ b/MatrixSDK/Crypto/MXOlmDevice.m
@@ -349,11 +349,12 @@ - (BOOL)addInboundGroupSession:(NSString*)sessionId
                         roomId:(NSString*)roomId
                      senderKey:(NSString*)senderKey
   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)
     {
         session = [[MXOlmInboundGroupSession alloc] initWithImportedSessionKey:sessionKey];
@@ -370,17 +371,33 @@ - (BOOL)addInboundGroupSession:(NSString*)sessionId
     }
     
     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;
+            }
         }
     }
 
     MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Add megolm session %@|%@ (import: %@)", senderKey, sessionId, exportFormat ? @"YES" : @"NO");
 
@@ -392,10 +409,11 @@ - (BOOL)addInboundGroupSession:(NSString*)sessionId
 
     session.senderKey = senderKey;
     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)
     {
         session.sharedHistory = sharedHistory;
@@ -404,10 +422,34 @@ - (BOOL)addInboundGroupSession:(NSString*)sessionId
     [self storeInboundGroupSessions:@[session]];
 
     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];
 
     for (MXMegolmSessionData *sessionData in inboundGroupSessionsData)
diff --git a/MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfo.swift b/MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfo.swift
new file mode 100644
index 00000000..b5bd6512
--- /dev/null
+++ b/MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfo.swift
@@ -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
+        )
+    }
+}
diff --git a/MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfoFactory.swift b/MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfoFactory.swift
new file mode 100644
index 00000000..315b81ee
--- /dev/null
+++ b/MatrixSDK/Crypto/RoomKeys/MXRoomKeyInfoFactory.swift
@@ -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
+    }
+}
diff --git a/MatrixSDK/Crypto/RoomKeys/MXRoomKeyResult.swift b/MatrixSDK/Crypto/RoomKeys/MXRoomKeyResult.swift
new file mode 100644
index 00000000..156604c4
--- /dev/null
+++ b/MatrixSDK/Crypto/RoomKeys/MXRoomKeyResult.swift
@@ -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
+    }
+}
diff --git a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransaction.m b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransaction.m
index 1367085b..d31d4371 100644
--- a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransaction.m
+++ b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransaction.m
@@ -361,19 +361,28 @@ - (void)verifyMacs
         }
         
 
         __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)
         {
             MXKey *key = [[MXKey alloc] initWithKeyFullId:keyFullId value:self.theirMac.mac[keyFullId]];
 
             // Check MAC with device keys
             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]]])
                 {
                     // Mark device as verified
                     MXLogDebug(@"[MXKeyVerification][MXSASTransaction] verifyMacs: Mark device %@ as verified", device);
@@ -396,11 +405,10 @@ - (void)verifyMacs
                 }
             }
             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
                     if ([key.value isEqualToString:[self macUsingAgreedMethod:otherUserMasterKeys.keys
                                                                          info:[NSString stringWithFormat:@"%@%@", baseInfo, keyFullId]]])
diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m
index 0d447a5b..93c47963 100644
--- a/MatrixSDK/MXSession.m
+++ b/MatrixSDK/MXSession.m
@@ -1962,20 +1962,29 @@ - (void)updateSummaryDirectUserIdForRooms:(NSSet<NSString*> *)roomIds
     }
 }
 
 - (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)
             {
                 dispatch_group_enter(dispatchGroup);
                 [self handleToDeviceEvent:event onComplete:^{
diff --git a/MatrixSDK/MatrixSDK.h b/MatrixSDK/MatrixSDK.h
index c39fa05d..1932910d 100644
--- a/MatrixSDK/MatrixSDK.h
+++ b/MatrixSDK/MatrixSDK.h
@@ -169,10 +169,12 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion;
 #import "MXMegolmDecryption.h"
 #import "MXOlmDecryption.h"
 #import "MXCachedSyncResponse.h"
 #import "MXBackgroundCryptoStore.h"
 #import "MXSharedHistoryKeyService.h"
+#import "MXRoomKeyEventContent.h"
+#import "MXForwardedRoomKeyEventContent.h"
 
 //  Sync response models
 #import "MXSyncResponse.h"
 #import "MXPresenceSyncResponse.h"
 #import "MXToDeviceSyncResponse.h"
diff --git a/MatrixSDK/Utils/Categories/Dictionary.swift b/MatrixSDK/Utils/Categories/Dictionary.swift
new file mode 100644
index 00000000..27e0d85e
--- /dev/null
+++ b/MatrixSDK/Utils/Categories/Dictionary.swift
@@ -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
+    }
+}
diff --git a/MatrixSDK/Utils/MXDateProvider.swift b/MatrixSDK/Utils/MXDateProvider.swift
new file mode 100644
index 00000000..639acbbd
--- /dev/null
+++ b/MatrixSDK/Utils/MXDateProvider.swift
@@ -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()
+    }
+}
diff --git a/MatrixSDK/Utils/MXLRUCache.m b/MatrixSDK/Utils/MXLRUCache.m
index 116478c5..9d11cecf 100644
--- a/MatrixSDK/Utils/MXLRUCache.m
+++ b/MatrixSDK/Utils/MXLRUCache.m
@@ -147,10 +147,15 @@ - (void)put:(NSString*)key object:(NSObject*)object
                 }
                 
                 [cachedObjects addObject:item];
                 [cachedKeys addObject:key];
             }
+            else
+            {
+                MXLRUCacheItem* item = [cachedObjects objectAtIndex:pos];
+                item.object = object;
+            }
         }
     }
 }
 
 /**
diff --git a/MatrixSDK/Utils/MXTools.swift b/MatrixSDK/Utils/MXTools.swift
index 140e0b5c..f427d58d 100644
--- a/MatrixSDK/Utils/MXTools.swift
+++ b/MatrixSDK/Utils/MXTools.swift
@@ -42,6 +42,35 @@ public extension MXTools {
             hasQueryParameters = true
             urlString.append(parameterFormat)
         }
         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
+    }
 }
diff --git a/MatrixSDKTests/Crypto/Algorithms/Megolm/MXMegolmDecryptionUnitTests.swift b/MatrixSDKTests/Crypto/Algorithms/Megolm/MXMegolmDecryptionUnitTests.swift
index 3521699f..77638e57 100644
--- a/MatrixSDKTests/Crypto/Algorithms/Megolm/MXMegolmDecryptionUnitTests.swift
+++ b/MatrixSDKTests/Crypto/Algorithms/Megolm/MXMegolmDecryptionUnitTests.swift
@@ -33,11 +33,12 @@ class MXMegolmDecryptionUnitTests: XCTestCase {
             roomId: String!,
             senderKey: String!,
             forwardingCurve25519KeyChain: [String]!,
             keysClaimed: [String : String]!,
             exportFormat: Bool,
-            sharedHistory: Bool
+            sharedHistory: Bool,
+            untrusted: Bool
         ) -> Bool {
             sessions.append(
                 .init(sharedHistory: sharedHistory)
             )
             return true
@@ -135,11 +136,13 @@ class MXMegolmDecryptionUnitTests: XCTestCase {
             (true, true)
         ]
         session.historyVisibility = kMXRoomHistoryVisibilityWorldReadable
         
         for (eventValue, expectedValue) in eventToExpectation {
-            let event = makeRoomKeyEvent(sharedHistory: eventValue)
+            let event = MXEvent.roomKeyFixture(
+                sharedHistory: eventValue
+            )
             device.sessions = []
             
             decryption.onRoomKeyEvent(event)
             
             XCTAssertEqual(device.sessions.count, 1)
@@ -211,31 +214,6 @@ class MXMegolmDecryptionUnitTests: XCTestCase {
             
             let hasSharedHistory = decryption.hasSharedHistory(forRoomId: roomId1, sessionId: sessionId1, senderKey: senderKey)
             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
-    }
 }
diff --git a/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift
new file mode 100644
index 00000000..4194c89c
--- /dev/null
+++ b/MatrixSDKTests/Crypto/Data/Store/MXMemoryCryptoStore.swift
@@ -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
+    }
+}
diff --git a/MatrixSDKTests/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContentUnitTests.swift b/MatrixSDKTests/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContentUnitTests.swift
new file mode 100644
index 00000000..21e6c66b
--- /dev/null
+++ b/MatrixSDKTests/Crypto/KeySharing/Data/MXForwardedRoomKeyEventContentUnitTests.swift
@@ -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)
+    }
+}
diff --git a/MatrixSDKTests/Crypto/KeySharing/Data/MXRoomKeyEventContentUnitTests.swift b/MatrixSDKTests/Crypto/KeySharing/Data/MXRoomKeyEventContentUnitTests.swift
new file mode 100644
index 00000000..79ffdcd6
--- /dev/null
+++ b/MatrixSDKTests/Crypto/KeySharing/Data/MXRoomKeyEventContentUnitTests.swift
@@ -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)
+    }
+}
diff --git a/MatrixSDKTests/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManagerUnitTests.swift b/MatrixSDKTests/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManagerUnitTests.swift
new file mode 100644
index 00000000..59248d51
--- /dev/null
+++ b/MatrixSDKTests/Crypto/KeySharing/MXUnrequestedForwardedRoomKeyManagerUnitTests.swift
@@ -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
+        )
+    }
+}
diff --git a/MatrixSDKTests/Crypto/MXOlmDeviceUnitTests.swift b/MatrixSDKTests/Crypto/MXOlmDeviceUnitTests.swift
index 4986ed88..6ccabdc3 100644
--- a/MatrixSDKTests/Crypto/MXOlmDeviceUnitTests.swift
+++ b/MatrixSDKTests/Crypto/MXOlmDeviceUnitTests.swift
@@ -69,11 +69,12 @@ class MXOlmDeviceUnitTests: XCTestCase {
             roomId: roomId,
             senderKey: senderKey,
             forwardingCurve25519KeyChain: [],
             keysClaimed: [:],
             exportFormat: false,
-            sharedHistory: sharedHistory
+            sharedHistory: sharedHistory,
+            untrusted: false
         )
     }
     
     func test_addInboundGroupSession_storesSharedHistory() {
         let session = device.createOutboundGroupSessionForRoom(withRoomId: roomId)!
diff --git a/MatrixSDKTests/Crypto/RoomKeys/MXRoomKeyInfoFactoryUnitTests.swift b/MatrixSDKTests/Crypto/RoomKeys/MXRoomKeyInfoFactoryUnitTests.swift
new file mode 100644
index 00000000..609374da
--- /dev/null
+++ b/MatrixSDKTests/Crypto/RoomKeys/MXRoomKeyInfoFactoryUnitTests.swift
@@ -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)
+    }
+}
diff --git a/MatrixSDKTests/JSONModels/MXEventFixtures.swift b/MatrixSDKTests/JSONModels/MXEventFixtures.swift
index 0e021a77..a934339e 100644
--- a/MatrixSDKTests/JSONModels/MXEventFixtures.swift
+++ b/MatrixSDKTests/JSONModels/MXEventFixtures.swift
@@ -32,6 +32,74 @@ extension MXEvent {
                     kMXEventContentRelatesToKeyRelationType: MXEventRelationTypeThread
                 ]
             ]
         ])!
     }
+    
+    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
+    }
 }
diff --git a/MatrixSDKTests/MXCryptoSecretShareTests.m b/MatrixSDKTests/MXCryptoSecretShareTests.m
index ebc76297..c3a1ee8a 100644
--- a/MatrixSDKTests/MXCryptoSecretShareTests.m
+++ b/MatrixSDKTests/MXCryptoSecretShareTests.m
@@ -80,11 +80,11 @@ - (void)testLocalSecretStorage
 /**
  Nomical case: Gossip a secret between 2 devices.
  
  - 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
  */
 - (void)testSecretShare
 {
@@ -99,12 +99,13 @@ - (void)testSecretShare
         // - Alice logs in on a new device
         [matrixSDKTestsE2EData loginUserOnANewDevice:self credentials:aliceSession.matrixRestClient.credentials withPassword:MXTESTS_ALICE_PWD onComplete:^(MXSession *newAliceSession) {
             
             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) {
                 XCTAssertNotNil(requestId);
             } onSecretReceived:^BOOL(NSString * _Nonnull sharedSecret) {
diff --git a/MatrixSDKTests/MXCryptoTests.m b/MatrixSDKTests/MXCryptoTests.m
index 857c3921..244dffb5 100644
--- a/MatrixSDKTests/MXCryptoTests.m
+++ b/MatrixSDKTests/MXCryptoTests.m
@@ -29,10 +29,11 @@
 #import "MXSDKOptions.h"
 #import "MXTools.h"
 #import "MXSendReplyEventDefaultStringLocalizer.h"
 #import "MXOutboundSessionInfo.h"
 #import <OLMKit/OLMKit.h>
+#import "MXLRUCache.h"
 
 #import "MXKey.h"
 
 #if 1 // MX_CRYPTO autamatic definiton does not work well for tests so force it
 //#ifdef MX_CRYPTO
@@ -2079,10 +2080,12 @@ - (void)testLateRoomKey
                 XCTAssert(toDeviceEvent);
                 NSString *sessionId = toDeviceEvent.content[@"session_id"];
 
                 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];
                 [bobSession decryptEvents:@[event] inTimeline:nil onComplete:^(NSArray<MXEvent *> *failedEvents) {
                     
diff --git a/MatrixSDKTests/MXToolsUnitTests.m b/MatrixSDKTests/MXToolsUnitTests.m
index 65b58da8..4e15a4e1 100644
--- a/MatrixSDKTests/MXToolsUnitTests.m
+++ b/MatrixSDKTests/MXToolsUnitTests.m
@@ -16,10 +16,11 @@
 
 #import <XCTest/XCTest.h>
 
 #import "MXTools.h"
 #import "MatrixSDKTestsSwiftHeader.h"
+#import "MatrixSDKSwiftHeader.h"
 
 @interface MXToolsUnitTests : XCTestCase
 
 @end
 
@@ -127,6 +128,56 @@ - (void)testUrlGeneration
     // if the URL is longer than kMXUrlMaxLength, no more parameter should be added
     XCTAssertEqualObjects(currentResult, [MXTools urlStringWithBase:currentResult queryParameters:@[parameter]]);
     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
diff --git a/MatrixSDKTests/TestPlans/UnitTests.xctestplan b/MatrixSDKTests/TestPlans/UnitTests.xctestplan
index a85e21f0..95bffdae 100644
--- a/MatrixSDKTests/TestPlans/UnitTests.xctestplan
+++ b/MatrixSDKTests/TestPlans/UnitTests.xctestplan
@@ -50,14 +50,16 @@
         "MXEventReferenceUnitTests",
         "MXEventScanStoreUnitTests",
         "MXEventsByTypesEnumeratorOnArrayTests",
         "MXEventsEnumeratorOnArrayTests",
         "MXFilterUnitTests",
+        "MXForwardedRoomKeyEventContentUnitTests",
         "MXHTTPAdditionalHeadersUnitTests",
         "MXJSONModelUnitTests",
         "MXKeyBackupUnitTests",
         "MXKeyProviderUnitTests",
+        "MXKeyVerificationRequestV2UnitTests",
         "MXMediaScanStoreUnitTests",
         "MXMegolmDecryptionUnitTests",
         "MXMegolmExportEncryptionUnitTests",
         "MXMegolmSessionDataUnitTests",
         "MXMemoryRoomStoreUnitTests",
@@ -65,19 +67,23 @@
         "MXOlmInboundGroupSessionUnitTests",
         "MXPushRuleUnitTests",
         "MXQRCodeDataUnitTests",
         "MXReplyEventParserUnitTests",
         "MXResponseUnitTests",
+        "MXRoomKeyEventContentUnitTests",
+        "MXRoomKeyInfoFactoryUnitTests",
         "MXRoomStateUnitTests",
+        "MXSASTransactionV2UnitTests",
         "MXSharedHistoryKeyManagerUnitTests",
         "MXStoreRoomListDataManagerUnitTests",
         "MXSyncResponseUnitTests",
         "MXTaskQueueUnitTests",
         "MXThreadEventTimelineUnitTests",
         "MXThreadingServiceUnitTests",
         "MXToolsUnitTests",
-        "MXTrustLevelSourceUnitTests"
+        "MXTrustLevelSourceUnitTests",
+        "MXUnrequestedForwardedRoomKeyManagerUnitTests",
       ],
       "target" : {
         "containerPath" : "container:MatrixSDK.xcodeproj",
         "identifier" : "B1E09A0D2397FA950057C069",
         "name" : "MatrixSDKTests-macOS"
diff --git a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan
index 846fc949..21cfbcea 100644
--- a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan
+++ b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan
@@ -58,14 +58,16 @@
         "MXErrorUnitTests",
         "MXEventAnnotationUnitTests",
         "MXEventReferenceUnitTests",
         "MXEventScanStoreUnitTests",
         "MXFilterUnitTests",
+        "MXForwardedRoomKeyEventContentUnitTests",
         "MXHTTPAdditionalHeadersUnitTests",
         "MXJSONModelUnitTests",
         "MXKeyBackupUnitTests",
         "MXKeyProviderUnitTests",
+        "MXKeyVerificationRequestV2UnitTests",
         "MXMediaScanStoreUnitTests",
         "MXMegolmDecryptionUnitTests",
         "MXMegolmExportEncryptionUnitTests",
         "MXMegolmSessionDataUnitTests",
         "MXMemoryRoomStoreUnitTests",
@@ -73,19 +75,23 @@
         "MXOlmInboundGroupSessionUnitTests",
         "MXPushRuleUnitTests",
         "MXQRCodeDataUnitTests",
         "MXReplyEventParserUnitTests",
         "MXResponseUnitTests",
+        "MXRoomKeyEventContentUnitTests",
+        "MXRoomKeyInfoFactoryUnitTests",
         "MXRoomStateUnitTests",
+        "MXSASTransactionV2UnitTests",
         "MXSharedHistoryKeyManagerUnitTests",
         "MXStoreRoomListDataManagerUnitTests",
         "MXSyncResponseUnitTests",
         "MXTaskQueueUnitTests",
         "MXThreadEventTimelineUnitTests",
         "MXThreadingServiceUnitTests",
         "MXToolsUnitTests",
-        "MXTrustLevelSourceUnitTests"
+        "MXTrustLevelSourceUnitTests",
+        "MXUnrequestedForwardedRoomKeyManagerUnitTests",
       ],
       "target" : {
         "containerPath" : "container:MatrixSDK.xcodeproj",
         "identifier" : "B1E09A0D2397FA950057C069",
         "name" : "MatrixSDKTests-macOS"

Vulnerable code:         guard let content = event.content else {
            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ERROR: incomplete event content: \(String(describing: event.jsonDictionary()))")
        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()))")
        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
        
        let sharedHistory = (content[kMXSharedHistoryKeyName] as? Bool) ?? false
        olmDevice.addInboundGroupSession(sessionId,
                                         sessionKey: sessionKey,
                                         roomId: roomId,
                                         senderKey: senderKey,
                                         forwardingCurve25519KeyChain: forwardingKeyChain,
                                         keysClaimed: keysClaimed,
                                         exportFormat: exportFormat,
                                         sharedHistory: sharedHistory)
        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]
@class MXCrypto, MXOlmInboundGroupSession;
 * Handle a key event.
 *
 * @param event the key 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)
        MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: ERROR: Key event is missing fields");

    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;
    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];
    [self retryDecryption:senderKey sessionId:content[@"session_id"] complete:^(BOOL allDecrypted) {
                                                 @"algorithm": content[@"algorithm"],
                                                 @"room_id": content[@"room_id"],
                                                 @"session_id": content[@"session_id"],
                                                 @"sender_key": senderKey
/**
 Flag indicating the backup trusted locally.
 */
@property (nonatomic, getter=isTrustedLocally) BOOL trustedLocally;

        _trustedLocally = NO;
    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;
        }
    }

    keyBackupVersionTrust.usable = keyBackupVersionTrust.usable || keyBackupVersionTrust.isTrustedLocally;
@interface MXCrypto ()
    id<MXEncrypting> alg = roomEncryptors[event.roomId];
    if (!alg)
    {
        // No encrypting in this room
        return;
    }

                 sharedHistory:(BOOL)sharedHistory;
                 sharedHistory:(BOOL)sharedHistory;
        MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Update for megolm session %@|%@", senderKey, sessionId);
        // If our existing session is better, we keep it
        if (existingSession.session.firstKnownIndex <= session.session.firstKnownIndex)
            MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Skip it. The index of the incoming session is higher (%@ vs %@)", @(session.session.firstKnownIndex), @(existingSession.session.firstKnownIndex));
            return NO;
                MXCrossSigningKey *otherUserMasterKeys= [self.manager.crypto crossSigningKeysForUser:self.otherDevice.userId].masterKeys;
    if (events.count == 0)
    [self decryptEvents:events inTimeline:nil onComplete:^(NSArray<MXEvent *> *failedEvents) {
        for (MXEvent *event in events)
            sharedHistory: Bool
            let event = makeRoomKeyEvent(sharedHistory: eventValue)
    
    // 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
    }
            sharedHistory: sharedHistory
 - Alice trusts the new device
            // - Alice trusts the new device
        "MXTrustLevelSourceUnitTests"
        "MXTrustLevelSourceUnitTests"
Fixed code: ## 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))

		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, ); }; };
		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 */; };
		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>"; };
		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>"; };
				ED6DAC2028C7A4F000ECDCB6 /* MXDateProvider.swift */,
				ED6DAC0428C771D500ECDCB6 /* RoomKeys */,
				ED6DAC1A28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift */,
				ED01915128C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h */,
				ED01914E28C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m */,
				ED01914F28C64E0400ED3A69 /* MXRoomKeyEventContent.h */,
				ED01915028C64E0400ED3A69 /* MXRoomKeyEventContent.m */,
				ED6DAC0F28C7889A00ECDCB6 /* RoomKeys */,
				ED6DAC1328C78D3700ECDCB6 /* Store */,
				ED6DAC0528C77E0600ECDCB6 /* Data */,
				ED6DAC1D28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift */,
		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>";
		};
				ED6DAC0928C784AE00ECDCB6 /* Dictionary.swift */,
				ED01915828C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */,
				ED01915428C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */,
				ED01915928C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.h in Headers */,
				ED01915528C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */,
				ED6DAC0228C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */,
				ED01915228C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */,
				ED01915628C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */,
				ED6DAC1828C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */,
				ED6DAC1B28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */,
				ED6DAC0A28C784AE00ECDCB6 /* Dictionary.swift in Sources */,
				ED6DAC2228C7A51400ECDCB6 /* MXDateProvider.swift in Sources */,
				ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */,
				ED6DAC0728C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC0D28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC1128C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */,
				ED6DAC1528C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */,
				ED6DAC1E28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */,
				ED6DAC0328C76F0A00ECDCB6 /* MXRoomKeyInfo.swift in Sources */,
				ED01915328C64E0400ED3A69 /* MXForwardedRoomKeyEventContent.m in Sources */,
				ED01915728C64E0400ED3A69 /* MXRoomKeyEventContent.m in Sources */,
				ED6DAC1928C799E300ECDCB6 /* MXRoomKeyResult.swift in Sources */,
				ED6DAC1C28C79AA300ECDCB6 /* MXUnrequestedForwardedRoomKeyManager.swift in Sources */,
				ED6DAC0B28C784AE00ECDCB6 /* Dictionary.swift in Sources */,
				ED6DAC2128C7A51400ECDCB6 /* MXDateProvider.swift in Sources */,
				ED6DABFD28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */,
				ED6DAC0828C77E1100ECDCB6 /* MXForwardedRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC0E28C784E200ECDCB6 /* MXRoomKeyEventContentUnitTests.swift in Sources */,
				ED6DAC1228C788A700ECDCB6 /* MXRoomKeyInfoFactoryUnitTests.swift in Sources */,
				ED6DAC1628C78D4000ECDCB6 /* MXMemoryCryptoStore.swift in Sources */,
				ED6DAC1F28C79D2000ECDCB6 /* MXUnrequestedForwardedRoomKeyManagerUnitTests.swift in Sources */,
        //   only handle supported events
        guard MXTools.isSupportedToDeviceEvent(event) else {
            MXLog.debug("[MXBackgroundSyncService] handleToDeviceEvent: ignore unsupported event")
            return
        }
        
        guard let userId = credentials.userId else {
            MXLog.error("[MXBackgroundSyncService] handleToDeviceEvent: Cannot get userId")
        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")
        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")
    case secretRequest
    case secretSend
    case secretStorageDefaultKey
        case .secretRequest: return kMXEventTypeStringSecretRequest
        case .secretSend: return kMXEventTypeStringSecretSend
        case .secretStorageDefaultKey: return kMXEventTypeStringSecretStorageDefaultKey
        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]
@class MXCrypto, MXOlmInboundGroupSession, MXRoomKeyResult;
 Handle a key event.
 
 @param event the key event.
/**
 Handle new room key
 
 @param key the domain object with key details and safety
 */
- (void)onRoomKey:(MXRoomKeyResult*)key;

#import "MXForwardedRoomKeyEventContent.h"

// Factory to create room key info
@property (nonatomic, strong) MXRoomKeyInfoFactory *roomKeyInfoFactory;

        _roomKeyInfoFactory = [[MXRoomKeyInfoFactory alloc] initWithMyUserId:crypto.mxSession.credentials.userId store:crypto.store];
    MXRoomKeyResult *key = [self.roomKeyInfoFactory roomKeyFor:event];
    if (!key)
        MXLogError(@"[MXMegolmDecryption] onRoomKeyEvent: Cannot create megolm key from event");
    
    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;
}
- (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];
    [self retryDecryption:keyInfo.senderKey sessionId:keyInfo.sessionId complete:^(BOOL allDecrypted) {
                                                 @"algorithm": keyInfo.algorithm,
                                                 @"room_id": keyInfo.roomId,
                                                 @"session_id": keyInfo.sessionId,
                                                 @"sender_key": keyInfo.senderKey
                                   untrusted:NO
- (void)onRoomKeyInfo:(MXRoomKeyInfo *)keyInfo
{
    // No impact for olm
}

// 
// 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
// 
// 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
// 
// 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
// 
// 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
// 
// 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
    }
}
    if (![self canAcceptSecretSendEvent:event])
    {
        MXLogDebug(@"[MXSecretShareManager] handleSecretSendEvent: Rejecting unacceptable secret");
        return;
    }
    
- (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;
}


@interface MXCrypto () <MXUnrequestedForwardedRoomKeyManagerDelegate>
    // The manager for unrequested m.forwarded_room_keys
    MXUnrequestedForwardedRoomKeyManager *unrequestedForwardedRoomKeyManager;
    
        
        [self->unrequestedForwardedRoomKeyManager close];
        self->outgoingRoomKeyRequestManager = nil;
            [self->unrequestedForwardedRoomKeyManager processUnrequestedKeys];
        
        unrequestedForwardedRoomKeyManager = [[MXUnrequestedForwardedRoomKeyManager alloc] init];
        unrequestedForwardedRoomKeyManager.delegate = self;
- (void)handleUnrequestedRoomKeyInfo:(MXRoomKeyInfo *)keyInfo senderId:(NSString *)senderId senderKey:(NSString *)senderKey
{
    [unrequestedForwardedRoomKeyManager addPendingKeyWithKeyInfo:keyInfo senderId:senderId senderKey:senderKey];
}

    
    MXRoomMemberEventContent *content = [MXRoomMemberEventContent modelFromJSON:event.content];
    if ([userId isEqualToString:self.mxSession.credentials.userId] && [content.membership isEqualToString:kMXMembershipStringInvite])
    {
        [unrequestedForwardedRoomKeyManager onRoomInviteWithRoomId:event.roomId senderId:event.sender];
    }
    
#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];
}

@class MXRoomKeyInfo;

/**
 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;

                 sharedHistory:(BOOL)sharedHistory
                     untrusted:(BOOL)untrusted;
                 sharedHistory:(BOOL)sharedHistory
                     untrusted:(BOOL)untrusted
        MXLogDebug(@"[MXOlmDevice] addInboundGroupSession: Considering updates for megolm session %@|%@", senderKey, sessionId);
        BOOL isExistingSessionBetter = existingSession.session.firstKnownIndex <= session.session.firstKnownIndex;
        if (isExistingSessionBetter)
            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;
            }
    session.untrusted = untrusted;
- (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];
}

// 
// 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
        )
    }
}
// 
// 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
    }
}
// 
// 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
    }
}
        
        MXCrossSigningKey *otherUserMasterKeys= [self.manager.crypto crossSigningKeysForUser:self.otherDevice.userId].masterKeys;
                if ([device.deviceId isEqualToString:otherUserMasterKeys.keys])
                {
                    MXLogWarning(@"[MXKeyVerification][MXSASTransaction] verifyMacs: Device id should not be the same as master key");
                    cancelCode = MXTransactionCancelCode.invalidMessage;
                    break;
                }
                
    NSMutableArray *supportedEvents = [NSMutableArray arrayWithCapacity:events.count];
    for (MXEvent *event in events)
    {
        if ([MXTools isSupportedToDeviceEvent:event])
        {
            [supportedEvents addObject:event];
        }
    }

    if (supportedEvents.count == 0)
    [self decryptEvents:supportedEvents inTimeline:nil onComplete:^(NSArray<MXEvent *> *failedEvents) {
        for (MXEvent *event in supportedEvents)
#import "MXRoomKeyEventContent.h"
#import "MXForwardedRoomKeyEventContent.h"
// 
// 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
    }
}
// 
// 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()
    }
}
            else
            {
                MXLRUCacheItem* item = [cachedObjects objectAtIndex:pos];
                item.object = object;
            }

    @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
    }
            sharedHistory: Bool,
            untrusted: Bool
            let event = MXEvent.roomKeyFixture(
                sharedHistory: eventValue
            )
//
// 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
    }
}
// 
// 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)
    }
}
//
// 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)
    }
}
// 
// 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
        )
    }
}
            sharedHistory: sharedHistory,
            untrusted: false
// 
// 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)
    }
}
    
    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
    }
 - Alice trusts the new device and vice versa
            // - Alice trusts the new device and vice versa
            [newAliceSession.crypto setDeviceVerification:MXDeviceVerified forDevice:aliceSession.myDeviceId ofUser:aliceSession.myUserId success:nil failure:nil];
#import "MXLRUCache.h"
                MXLRUCache *cache = [bobSession.crypto.olmDevice valueForKey:@"inboundGroupSessionCache"];
                [cache clear];
#import "MatrixSDKSwiftHeader.h"
#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]);
}


        "MXForwardedRoomKeyEventContentUnitTests",
        "MXKeyVerificationRequestV2UnitTests",
        "MXRoomKeyEventContentUnitTests",
        "MXRoomKeyInfoFactoryUnitTests",
        "MXSASTransactionV2UnitTests",
        "MXTrustLevelSourceUnitTests",
        "MXUnrequestedForwardedRoomKeyManagerUnitTests",
        "MXForwardedRoomKeyEventContentUnitTests",
        "MXKeyVerificationRequestV2UnitTests",
        "MXRoomKeyEventContentUnitTests",
        "MXRoomKeyInfoFactoryUnitTests",
        "MXSASTransactionV2UnitTests",
        "MXTrustLevelSourceUnitTests",
        "MXUnrequestedForwardedRoomKeyManagerUnitTests",