diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index df5a338e32..9425501ea9 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1787,6 +1787,10 @@ EDB4209627DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209427DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift */; }; EDB4209927DF842F0036AF39 /* MXEventFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209827DF842F0036AF39 /* MXEventFixtures.swift */; }; EDB4209A27DF842F0036AF39 /* MXEventFixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209827DF842F0036AF39 /* MXEventFixtures.swift */; }; + EDBCF336281A8ABD00ED5044 /* MXSharedHistoryKeyService.h in Headers */ = {isa = PBXBuildFile; fileRef = EDBCF335281A8AB900ED5044 /* MXSharedHistoryKeyService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDBCF337281A8ABE00ED5044 /* MXSharedHistoryKeyService.h in Headers */ = {isa = PBXBuildFile; fileRef = EDBCF335281A8AB900ED5044 /* MXSharedHistoryKeyService.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDBCF339281A8D3D00ED5044 /* MXSharedHistoryKeyService.m in Sources */ = {isa = PBXBuildFile; fileRef = EDBCF338281A8D3D00ED5044 /* MXSharedHistoryKeyService.m */; }; + EDBCF33A281A8D3D00ED5044 /* MXSharedHistoryKeyService.m in Sources */ = {isa = PBXBuildFile; fileRef = EDBCF338281A8D3D00ED5044 /* MXSharedHistoryKeyService.m */; }; EDF4678727E3331D00435913 /* EventsEnumeratorDataSourceStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF4678627E3331D00435913 /* EventsEnumeratorDataSourceStub.swift */; }; EDF4678827E3331D00435913 /* EventsEnumeratorDataSourceStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF4678627E3331D00435913 /* EventsEnumeratorDataSourceStub.swift */; }; F0173EAC1FCF0E8900B5F6A3 /* MXGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = F0173EAA1FCF0E8800B5F6A3 /* MXGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -2785,6 +2789,8 @@ EDB4209027DF77310036AF39 /* MXEventsEnumeratorOnArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXEventsEnumeratorOnArrayTests.swift; sourceTree = ""; }; EDB4209427DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXEventsByTypesEnumeratorOnArrayTests.swift; sourceTree = ""; }; EDB4209827DF842F0036AF39 /* MXEventFixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXEventFixtures.swift; sourceTree = ""; }; + EDBCF335281A8AB900ED5044 /* MXSharedHistoryKeyService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXSharedHistoryKeyService.h; sourceTree = ""; }; + EDBCF338281A8D3D00ED5044 /* MXSharedHistoryKeyService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXSharedHistoryKeyService.m; sourceTree = ""; }; EDC74874AB2D86EFEE912B04 /* Pods-MatrixSDK-MatrixSDK-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatrixSDK-MatrixSDK-macOS.debug.xcconfig"; path = "Target Support Files/Pods-MatrixSDK-MatrixSDK-macOS/Pods-MatrixSDK-MatrixSDK-macOS.debug.xcconfig"; sourceTree = ""; }; EDF4678627E3331D00435913 /* EventsEnumeratorDataSourceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsEnumeratorDataSourceStub.swift; sourceTree = ""; }; F0173EAA1FCF0E8800B5F6A3 /* MXGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXGroup.h; sourceTree = ""; }; @@ -4123,6 +4129,8 @@ 32A30B161FB4813400C8309E /* MXIncomingRoomKeyRequestManager.h */, 32A30B171FB4813400C8309E /* MXIncomingRoomKeyRequestManager.m */, ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */, + EDBCF335281A8AB900ED5044 /* MXSharedHistoryKeyService.h */, + EDBCF338281A8D3D00ED5044 /* MXSharedHistoryKeyService.m */, ); path = KeySharing; sourceTree = ""; @@ -5171,6 +5179,7 @@ 324DD2A6246AE81300377005 /* MXSecretStorageKeyContent.h in Headers */, EC60ED8F265CFD3B00B39A4E /* MXRoomSync.h in Headers */, ECD2899E26EB570B00F268CF /* MXRoomSummaryStore.h in Headers */, + EDBCF336281A8ABD00ED5044 /* MXSharedHistoryKeyService.h in Headers */, EC8A53C325B1BC77004E0802 /* MXCallInviteEventContent.h in Headers */, 3281E8B919E42DFE00976E1A /* MXJSONModels.h in Headers */, 3A108AA225810FE5005EEBE9 /* MXRawDataKey.h in Headers */, @@ -5675,6 +5684,7 @@ B14EF3432397E90400758AF0 /* MXRoomEventTimeline.h in Headers */, B14EF3442397E90400758AF0 /* NSArray+MatrixSDK.h in Headers */, B165B81225C3307E003CF7F7 /* MXLoginSSOIdentityProviderBrand.h in Headers */, + EDBCF337281A8ABE00ED5044 /* MXSharedHistoryKeyService.h in Headers */, 324DD2C6246E638B00377005 /* MXAesHmacSha2.h in Headers */, B14EF3452397E90400758AF0 /* MXReplyEventParser.h in Headers */, 323F878E25553D84009E9E67 /* MXTaskProfile.h in Headers */, @@ -6083,6 +6093,7 @@ 66836AB727CFA17200515780 /* MXEventStreamService.swift in Sources */, B11BD44922CB56790064D8B0 /* MXReplyEventParser.m in Sources */, EC0B941127184E8A00B4D440 /* MXRoomSummaryMO.swift in Sources */, + EDBCF339281A8D3D00ED5044 /* MXSharedHistoryKeyService.m in Sources */, EC0B941327184E8A00B4D440 /* MXRoomMembersCountMO.swift in Sources */, 323360701A403A0D0071A488 /* MXFileStore.m in Sources */, B1136967230C1E8600E2B2FA /* MXIdentityService.swift in Sources */, @@ -6629,6 +6640,7 @@ 66836AB827CFA17200515780 /* MXEventStreamService.swift in Sources */, 3A59A4A025A7A16F00DDA1FC /* MXOlmOutboundGroupSession.m in Sources */, EC0B941227184E8A00B4D440 /* MXRoomSummaryMO.swift in Sources */, + EDBCF33A281A8D3D00ED5044 /* MXSharedHistoryKeyService.m in Sources */, EC0B941427184E8A00B4D440 /* MXRoomMembersCountMO.swift in Sources */, B14EF1F92397E90400758AF0 /* MXReactionRelation.m in Sources */, B19A30BB2404268600FB6F35 /* MXQRCodeData.m in Sources */, diff --git a/MatrixSDK/Background/MXBackgroundSyncService.swift b/MatrixSDK/Background/MXBackgroundSyncService.swift index 1b37f240ee..ce248a8024 100644 --- a/MatrixSDK/Background/MXBackgroundSyncService.swift +++ b/MatrixSDK/Background/MXBackgroundSyncService.swift @@ -579,6 +579,7 @@ public enum MXBackgroundSyncServiceError: Error { return } + let sharedHistory = (content[kMXSharedHistoryKeyName] as? Bool) ?? isRoomSharingHistory(roomId: roomId) olmDevice.addInboundGroupSession(sessionId, sessionKey: sessionKey, roomId: roomId, @@ -586,7 +587,7 @@ public enum MXBackgroundSyncServiceError: Error { forwardingCurve25519KeyChain: forwardingKeyChain, keysClaimed: keysClaimed, exportFormat: exportFormat, - sharedHistory: isRoomSharingHistory(roomId: roomId)) + sharedHistory: sharedHistory) } private func isRoomSharingHistory(roomId: String) -> Bool { diff --git a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.h b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.h index 00071f56e4..77da986481 100644 --- a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.h +++ b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.h @@ -21,8 +21,7 @@ #ifdef MX_CRYPTO #import "MXDecrypting.h" - -@protocol MXSharedHistoryKeyService; +#import "MXSharedHistoryKeyService.h" @interface MXMegolmDecryption : NSObject diff --git a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m index c9ec611820..cda706b61d 100644 --- a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m +++ b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m @@ -25,6 +25,7 @@ #import "MXCrypto_Private.h" #import "MXTools.h" #import "MatrixSDKSwiftHeader.h" +#import "MXSharedHistoryKeyService.h" @interface MXMegolmDecryption () { @@ -212,11 +213,14 @@ - (void)onRoomKeyEvent:(MXEvent *)event NSArray *forwardingKeyChain; BOOL exportFormat = NO; NSDictionary *keysClaimed; + BOOL sharedHistory = [crypto isRoomSharingHistory:roomId]; + if (content[kMXSharedHistoryKeyName] != nil) { + MXJSONModelSetBoolean(sharedHistory, content[kMXSharedHistoryKeyName]); + } if (event.eventType == MXEventTypeRoomForwardedKey) { exportFormat = YES; - MXJSONModelSetArray(forwardingKeyChain, content[@"forwarding_curve25519_key_chain"]); if (!forwardingKeyChain) { @@ -254,7 +258,6 @@ - (void)onRoomKeyEvent:(MXEvent *)event MXLogDebug(@"[MXMegolmDecryption] onRoomKeyEvent: Adding key for megolm session %@|%@ from %@ event", senderKey, sessionId, event.type); - BOOL sharedHistory = [crypto isRoomSharingHistory:roomId]; [olmDevice addInboundGroupSession:sessionId sessionKey:sessionKey roomId:roomId @@ -530,16 +533,18 @@ - (void)requestKeysForEvent:(MXEvent*)event #pragma mark - MXSharedHistoryKeyStore -- (BOOL)hasSharedHistoryWithSessionId:(NSString *)sessionId senderKey:(NSString *)senderKey +- (BOOL)hasSharedHistoryForRoomId:(NSString *)roomId + sessionId:(NSString *)sessionId + senderKey:(NSString *)senderKey { MXOlmInboundGroupSession *session = [crypto.store inboundGroupSessionWithId:sessionId andSenderKey:senderKey]; - return session.sharedHistory; + return session.sharedHistory && [session.roomId isEqualToString:roomId]; } -- (void)shareKeysWithRequest:(MXSharedHistoryKeyRequest *)request - success:(void (^)(void))success - failure:(void (^)(NSError *error))failure +- (void)shareKeysForRequest:(MXSharedHistoryKeyRequest *)request + success:(void (^)(void))success + failure:(void (^)(NSError *))failure { [self shareKeysWitUserId:request.userId devices:request.devices diff --git a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m index b79af12b65..144641a66f 100644 --- a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m +++ b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m @@ -28,6 +28,7 @@ #import "MXTools.h" #import "MXOutboundSessionInfo.h" #import +#import "MXSharedHistoryKeyService.h" @interface MXMegolmEncryption () @@ -354,6 +355,7 @@ - (MXHTTPOperation*)shareKey:(MXOutboundSessionInfo*)session { NSString *sessionKey = session.session.sessionKey; NSUInteger chainIndex = session.session.messageIndex; + BOOL sharedHistory = [self isSessionSharingHistory:session]; NSDictionary *payload = @{ @"type": kMXEventTypeStringRoomKey, @@ -362,7 +364,8 @@ - (MXHTTPOperation*)shareKey:(MXOutboundSessionInfo*)session @"room_id": roomId, @"session_id": session.sessionId, @"session_key": sessionKey, - @"chain_index": @(chainIndex) + @"chain_index": @(chainIndex), + kMXSharedHistoryKeyName: @(sharedHistory) } }; diff --git a/MatrixSDK/Crypto/Data/MXMegolmSessionData.m b/MatrixSDK/Crypto/Data/MXMegolmSessionData.m index 380627546d..91ffda221a 100644 --- a/MatrixSDK/Crypto/Data/MXMegolmSessionData.m +++ b/MatrixSDK/Crypto/Data/MXMegolmSessionData.m @@ -15,6 +15,7 @@ */ #import "MXMegolmSessionData.h" +#import "MXSharedHistoryKeyService.h" @implementation MXMegolmSessionData @@ -28,7 +29,7 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary MXJSONModelSetString(sessionData.roomId, JSONDictionary[@"room_id"]); MXJSONModelSetString(sessionData.sessionId, JSONDictionary[@"session_id"]); MXJSONModelSetString(sessionData.sessionKey, JSONDictionary[@"session_key"]); - MXJSONModelSetBoolean(sessionData.sharedHistory, JSONDictionary[@"shared_history"]); + MXJSONModelSetBoolean(sessionData.sharedHistory, JSONDictionary[kMXSharedHistoryKeyName]); MXJSONModelSetString(sessionData.algorithm, JSONDictionary[@"algorithm"]); MXJSONModelSetArray(sessionData.forwardingCurve25519KeyChain, JSONDictionary[@"forwarding_curve25519_key_chain"]) } @@ -44,7 +45,7 @@ - (NSDictionary *)JSONDictionary @"room_id": _roomId, @"session_id": _sessionId, @"session_key":_sessionKey, - @"shared_history": @(_sharedHistory), + kMXSharedHistoryKeyName: @(_sharedHistory), @"algorithm": _algorithm, @"forwarding_curve25519_key_chain": _forwardingCurve25519KeyChain ? _forwardingCurve25519KeyChain : @[] }; diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m index 95ac783101..221ce9a2a2 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m @@ -29,6 +29,7 @@ #import "MXKeyProvider.h" #import "MXRawDataKey.h" #import "MXCrossSigning_Private.h" +#import "MXSharedHistoryKeyService.h" #pragma mark - Constants definitions @@ -1612,7 +1613,7 @@ - (MXKeyBackupData*)encryptGroupSession:(MXOlmInboundGroupSession*)session @"sender_claimed_keys": sessionData.senderClaimedKeys, @"forwarding_curve25519_key_chain": sessionData.forwardingCurve25519KeyChain ? sessionData.forwardingCurve25519KeyChain : @[], @"session_key": sessionData.sessionKey, - @"shared_history": @(sessionData.sharedHistory) + kMXSharedHistoryKeyName: @(sessionData.sharedHistory) }; OLMPkMessage *encryptedSessionBackupData = [_backupKey encryptMessage:[MXTools serialiseJSONObject:sessionBackupData] error:nil]; if (![self checkOLMPkMessage:encryptedSessionBackupData]) diff --git a/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyManager.swift b/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyManager.swift index d28f86f397..a9bcd201dd 100644 --- a/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyManager.swift +++ b/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyManager.swift @@ -16,13 +16,6 @@ import Foundation -/// Object managing the session keys and responsible for executing key share requests -@objc -public protocol MXSharedHistoryKeyService { - func hasSharedHistory(sessionId: String, senderKey: String) -> Bool - func shareKeys(request: MXSharedHistoryKeyRequest, success: (() -> Void)?, failure: ((NSError?) -> Void)?) -} - /// Manager responsible for sharing keys of messages in a room with an invited user /// /// The intent of sharing keys with different users on invite is to allow them to see any immediate @@ -34,15 +27,16 @@ public protocol MXSharedHistoryKeyService { @objc public class MXSharedHistoryKeyManager: NSObject { struct SessionInfo: Hashable { - let roomId: String let sessionId: String let senderKey: String } + private let roomId: String private let crypto: MXCrypto private let service: MXSharedHistoryKeyService - @objc public init(crypto: MXCrypto, service: MXSharedHistoryKeyService) { + @objc public init(roomId: String, crypto: MXCrypto, service: MXSharedHistoryKeyService) { + self.roomId = roomId self.crypto = crypto self.service = service } @@ -74,12 +68,12 @@ public class MXSharedHistoryKeyManager: NSObject { let request = MXSharedHistoryKeyRequest( userId: userId, devices: devices, - roomId: session.roomId, + roomId: roomId, sessionId: session.sessionId, senderKey: session.senderKey ) - service.shareKeys(request: request) { + service.shareKeys(for: request) { // Success does not trigger any further action / user notification, so we only log the outcome MXLog.debug("[MXSharedHistoryRoomKeyRequestManager] Shared key successfully") } failure: { @@ -101,21 +95,19 @@ public class MXSharedHistoryKeyManager: NSObject { private func sessionInfo(for message: MXEvent) -> SessionInfo? { let content = message.wireContent guard - let roomId = message.roomId, let sessionId = content?["session_id"] as? String, let senderKey = content?["sender_key"] as? String else { MXLog.debug("[MXSharedHistoryRoomKeyRequestManager] Cannot create key request") return nil } - - guard service.hasSharedHistory(sessionId: sessionId, senderKey: senderKey) else { - MXLog.debug("[MXSharedHistoryRoomKeyRequestManager] Skipping keys for message without shared history") + + guard service.hasSharedHistory(forRoomId: roomId, sessionId: sessionId, senderKey: senderKey) else { + MXLog.debug("[MXSharedHistoryRoomKeyRequestManager] Skipping keys for message without shared history or mismatched room identifier") return nil } return .init( - roomId: roomId, sessionId: sessionId, senderKey: senderKey ) diff --git a/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyService.h b/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyService.h new file mode 100644 index 0000000000..b8245c79e5 --- /dev/null +++ b/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyService.h @@ -0,0 +1,48 @@ +// +// 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. +// + +#ifndef MXSharedHistoryKeyService_h +#define MXSharedHistoryKeyService_h + +/** + Name of the field for `sharedHistory` flag when sharing, exporting or backing up keys + */ +FOUNDATION_EXPORT NSString *const kMXSharedHistoryKeyName; + +@class MXSharedHistoryKeyRequest; + +/** + Object managing the session keys and responsible for executing key share requests + */ +@protocol MXSharedHistoryKeyService + +/** + Check whether key for a given session (sessionId + senderKey) exists + */ +- (BOOL)hasSharedHistoryForRoomId:(NSString *)roomId + sessionId:(NSString *)sessionId + senderKey:(NSString *)senderKey; + +/** + Share keys for a given request, containing userId, list of devices and session to share + */ +- (void)shareKeysForRequest:(MXSharedHistoryKeyRequest *)request + success:(void(^)(void))success + failure:(void(^)(NSError *))failure; + +@end + +#endif /* MXSharedHistoryKeyService_h */ diff --git a/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyService.m b/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyService.m new file mode 100644 index 0000000000..96fcd88d88 --- /dev/null +++ b/MatrixSDK/Crypto/KeySharing/MXSharedHistoryKeyService.m @@ -0,0 +1,19 @@ +// +// 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 + +NSString *const kMXSharedHistoryKeyName = @"org.matrix.msc3061.shared_history"; diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index 47ec8f5eb5..bc44e5b206 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -52,6 +52,7 @@ #import "MXDeviceListResponse.h" #import "MatrixSDKSwiftHeader.h" +#import "MXSharedHistoryKeyService.h" /** The store to use for crypto. */ @@ -2506,7 +2507,8 @@ - (NSDictionary*)buildMegolmKeyForwardingMessage:(NSString*)roomId senderKey:(NS @"session_id": sessionId, @"session_key": key[@"key"], @"chain_index": key[@"chain_index"], - @"forwarding_curve25519_key_chain": key[@"forwarding_curve25519_key_chain"] + @"forwarding_curve25519_key_chain": key[@"forwarding_curve25519_key_chain"], + kMXSharedHistoryKeyName: key[@"shared_history"] } }; } diff --git a/MatrixSDK/Data/MXRoom.m b/MatrixSDK/Data/MXRoom.m index b98eb2e44f..2d59cea4d1 100644 --- a/MatrixSDK/Data/MXRoom.m +++ b/MatrixSDK/Data/MXRoom.m @@ -125,7 +125,9 @@ - (id)initWithRoomId:(NSString *)roomId matrixSession:(MXSession *)mxSession2 an if (mxSession.crypto) { MXMegolmDecryption *decryption = [[MXMegolmDecryption alloc] initWithCrypto:mxSession.crypto]; - sharedHistoryKeyManager = [[MXSharedHistoryKeyManager alloc] initWithCrypto:mxSession.crypto service:decryption]; + sharedHistoryKeyManager = [[MXSharedHistoryKeyManager alloc] initWithRoomId:roomId + crypto:mxSession.crypto + service:decryption]; } if (store) diff --git a/MatrixSDK/MatrixSDK.h b/MatrixSDK/MatrixSDK.h index 5f43bb5457..fe2278571f 100644 --- a/MatrixSDK/MatrixSDK.h +++ b/MatrixSDK/MatrixSDK.h @@ -162,6 +162,7 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXOlmDecryption.h" #import "MXCachedSyncResponse.h" #import "MXBackgroundCryptoStore.h" +#import "MXSharedHistoryKeyService.h" // Sync response models #import "MXSyncResponse.h" diff --git a/MatrixSDKTests/Crypto/Data/MXMegolmSessionDataUnitTests.swift b/MatrixSDKTests/Crypto/Data/MXMegolmSessionDataUnitTests.swift index 4d66a7d2e9..4f6cc36928 100644 --- a/MatrixSDKTests/Crypto/Data/MXMegolmSessionDataUnitTests.swift +++ b/MatrixSDKTests/Crypto/Data/MXMegolmSessionDataUnitTests.swift @@ -25,7 +25,7 @@ class MXMegolmSessionDataUnitTests: XCTestCase { "room_id": "D", "session_id": "E", "session_key": "F", - "shared_history": true, + "org.matrix.msc3061.shared_history": true, "algorithm": "G", "forwarding_curve25519_key_chain": ["H", "I"] ] @@ -61,7 +61,7 @@ class MXMegolmSessionDataUnitTests: XCTestCase { "room_id": "D", "session_id": "E", "session_key": "F", - "shared_history": true, + "org.matrix.msc3061.shared_history": true, "algorithm": "G", "forwarding_curve25519_key_chain": ["H", "I"] ]) diff --git a/MatrixSDKTests/Crypto/KeySharing/MXSharedHistoryKeyManagerUnitTests.swift b/MatrixSDKTests/Crypto/KeySharing/MXSharedHistoryKeyManagerUnitTests.swift index 135e472b54..f688e14493 100644 --- a/MatrixSDKTests/Crypto/KeySharing/MXSharedHistoryKeyManagerUnitTests.swift +++ b/MatrixSDKTests/Crypto/KeySharing/MXSharedHistoryKeyManagerUnitTests.swift @@ -28,17 +28,25 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { } } - class SpyService: MXSharedHistoryKeyService { - var sharedHistory: Set? - func hasSharedHistory(sessionId: String, senderKey: String) -> Bool { + class SpyService: NSObject, MXSharedHistoryKeyService { + struct SessionStub: Hashable { + let roomId: String + let sessionId: String + let senderKey: String + } + + var sharedHistory: Set? + func hasSharedHistory(forRoomId roomId: String!, sessionId: String!, senderKey: String!) -> Bool { guard let sharedHistory = sharedHistory else { return true } - return sharedHistory.contains(sessionId) + + let session = SessionStub(roomId: roomId, sessionId: sessionId, senderKey: senderKey) + return sharedHistory.contains(session) } var requests = [MXSharedHistoryKeyRequest]() - func shareKeys(request: MXSharedHistoryKeyRequest, success: (() -> Void)?, failure: ((NSError?) -> Void)?) { + func shareKeys(for request: MXSharedHistoryKeyRequest!, success: (() -> Void)!, failure: ((Error?) -> Void)!) { requests.append(request) success?() } @@ -76,7 +84,6 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { crypto.devices.setObject(MXDeviceInfo(deviceId: "1"), forUser: "user1", andDevice: "1") service = SpyService() - manager = MXSharedHistoryKeyManager(crypto: crypto, service: service) } private func makeEvent( @@ -84,7 +91,7 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { senderKey: String = "456" ) -> MXEvent { MXEvent(fromJSON: [ - "room_id": "123", + "room_id": "ABC", "type": kMXEventTypeStringRoomEncrypted, "content": [ "session_id": sessionId, @@ -93,13 +100,35 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { ]) } + private func makeInboundSession( + roomId: String = "ABC", + sessionId: String = "123", + senderKey: String = "456" + ) -> SpyService.SessionStub { + return .init(roomId: roomId, sessionId: sessionId, senderKey: senderKey) + } + + private func shareKeys( + userId: String = "user1", + roomId: String = "ABC", + enumerator: MXEventsEnumerator? = nil, + limit: Int = .max + ) { + manager = MXSharedHistoryKeyManager(roomId: roomId, crypto: crypto, service: service) + manager.shareMessageKeys( + withUserId: userId, + messageEnumerator: enumerator ?? self.enumerator, + limit: limit + ) + } + func testDoesNotCreateRequestIfNoKnownDevices() { enumerator.messages = [ makeEvent(sessionId: "A", senderKey: "B") ] crypto.devices = MXUsersDevicesMap() - manager.shareMessageKeys(withUserId: "user1", messageEnumerator: enumerator, limit: .max) + shareKeys() XCTAssertEqual(service.requests.count, 0) } @@ -112,7 +141,7 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { crypto.devices.setObject(MXDeviceInfo(deviceId: "2"), forUser: "user1", andDevice: "2") crypto.devices.setObject(MXDeviceInfo(deviceId: "3"), forUser: "user2", andDevice: "3") - manager.shareMessageKeys(withUserId: "user1", messageEnumerator: enumerator, limit: .max) + shareKeys() XCTAssertEqual(service.requests.count, 1) XCTAssertEqual( @@ -123,7 +152,7 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { MXDeviceInfo(deviceId: "1"), MXDeviceInfo(deviceId: "2") ], - roomId: "123", + roomId: "ABC", sessionId: "A", senderKey: "B" ) @@ -141,7 +170,7 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { makeEvent(sessionId: "3", senderKey: "B"), ] - manager.shareMessageKeys(withUserId: "user1", messageEnumerator: enumerator, limit: .max) + shareKeys() let identifiers = service.requests.map { [$0.sessionId, $0.senderKey] } XCTAssertEqual(service.requests.count, 5) @@ -161,7 +190,7 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { makeEvent(sessionId: "1"), ] - manager.shareMessageKeys(withUserId: "user1", messageEnumerator: enumerator, limit: 3) + shareKeys(limit: 3) let identifiers = service.requests.map { $0.sessionId } XCTAssertEqual(service.requests.count, 3) @@ -177,17 +206,44 @@ class MXSharedHistoryKeyManagerUnitTests: XCTestCase { makeEvent(sessionId: "5"), ] service.sharedHistory = [ - "1", - "2", - "4", + makeInboundSession(sessionId: "1"), + makeInboundSession(sessionId: "2"), + makeInboundSession(sessionId: "4"), ] - manager.shareMessageKeys(withUserId: "user1", messageEnumerator: enumerator, limit: .max) + shareKeys() let identifiers = service.requests.map { $0.sessionId } XCTAssertEqual(service.requests.count, 3) XCTAssertEqual(Set(identifiers), ["1", "2", "4"]) } + + func testIgnoresEventsWithMismatchedRoomId() { + enumerator.messages = [ + makeEvent(sessionId: "1"), + makeEvent(sessionId: "2"), + makeEvent(sessionId: "3"), + ] + service.sharedHistory = [ + makeInboundSession( + roomId: "XYZ", + sessionId: "1" + ), + makeInboundSession( + roomId: "ABC", + sessionId: "2" + ), + makeInboundSession( + roomId: "XYZ", + sessionId: "3" + ), + ] + + shareKeys(roomId: "ABC") + + XCTAssertEqual(service.requests.count, 1) + XCTAssertEqual(service.requests.first?.sessionId, "2") + } } extension MXSharedHistoryKeyRequest { diff --git a/MatrixSDKTests/MXCryptoShareTests.m b/MatrixSDKTests/MXCryptoShareTests.m index fe905c0b89..5d556b616b 100644 --- a/MatrixSDKTests/MXCryptoShareTests.m +++ b/MatrixSDKTests/MXCryptoShareTests.m @@ -21,6 +21,7 @@ #import "MXCrypto_Private.h" #import "MXCryptoStore.h" +#import "MXMemoryStore.h" #import @@ -571,6 +572,228 @@ - (void)testNotBetterSharedSession }]; } +/** + Test that we share the correct session keys for encrypted rooms when inviting + another user to the room, so that they can read any immediate context relevant + to their invite. + + - Alice creates a new room + -> She has no inbound session keys so far + - Alice sends one message to the room + -> Alice has one inbound session keys + - She changes the room's history visibility to not shared and sends another message + -> Alice now has two inbdound session keys, one with `sharedHistory` true and the other false + - She changes the visibility back to shared and sends last message + -> Alice now has 3 keys, 2 with `sharedHistory`, one without + - Alice invites Bob into the room + -> Bob has recieved only 2 session keys, namely those with `sharedHistory` set to true + */ +- (void)testShareHistoryKeysWithInvitedUser +{ + [matrixSDKTestsE2EData doE2ETestWithAliceInARoom:self + andStore:[[MXMemoryStore alloc] init] + readyToTest:^(MXSession *aliceSession, NSString *roomId, XCTestExpectation *expectation) + { + void (^failureBlock)(NSError *) = ^(NSError *error) + { + XCTFail("Test failure - %@", error); + [expectation fulfill]; + }; + + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; + [matrixSDKTestsData doMXSessionTestWithBob:nil + readyToTest:^(MXSession *bobSession, XCTestExpectation *expectation2) + { + // No keys present at the beginning + MXRoom *room = [aliceSession roomWithRoomId:roomId]; + XCTAssertEqual([self numberOfKeysInSession:aliceSession], 0); + + [self sendMessage:@"Hello" room:room success:^{ + // Sending one message will create the first session + XCTAssertEqual([self numberOfKeysInSession:aliceSession], 1); + + [self setHistoryVisibility:kMXRoomHistoryVisibilityJoined room:room success:^{ + [self sendMessage:@"Hi" room:room success:^{ + // The room visibility has changed, so sending another message will rotate + // megolm sessions, increasing to total of 2 + XCTAssertEqual([self numberOfKeysInSession:aliceSession], 2); + + [self setHistoryVisibility:kMXRoomHistoryVisibilityShared room:room success:^{ + [self sendMessage:@"How are you?" room:room success:^{ + // The room visibility has changed again, so another rotation leads to 3 sessions + XCTAssertEqual([self numberOfKeysInSession:aliceSession], 3); + + // Finally inviting a user (the outcome of this captured in the notification listener) + [room inviteUser:bobSession.myUser.userId success:^{ + } failure:failureBlock]; + } failure:failureBlock]; + } failure:failureBlock]; + } failure:failureBlock]; + } failure:failureBlock]; + } failure:failureBlock]; + + // Listen to a notification of to_device events, which will store keys on Bob's device + __block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionOnToDeviceEventNotification + object:bobSession + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notif) + { + + // Give some extra time, as we are storing keys in Realm + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + if (observer) + { + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + observer = nil; + + // Bob should only have recieved 2 keys, as the third Alice's key has `sharedHistory` set to false + XCTAssertEqual([self numberOfKeysInSession:bobSession], 2); + [expectation fulfill]; + } + }); + }]; + }]; + }]; +} + +/** + Test that we preserve the `sharedHistory` flag as we pass keys between different devices + and different users + + - Alice creates a new room, sends a few messages and logs in with another device + -> Her second device has the same amount of keys as the first, even if some of the keys do not have shared history + - Alice invites Bob into the room from her second device + -> Bob has recieved only 2 session keys, namely those with `sharedHistory` set to true + */ +- (void)testSharedHistoryPreservedWhenForwardingKeys +{ + [matrixSDKTestsE2EData doE2ETestWithAliceInARoom:self + andStore:[[MXMemoryStore alloc] init] + readyToTest:^(MXSession *aliceSession1, NSString *roomId, XCTestExpectation *expectation) + { + void (^failureBlock)(NSError *) = ^(NSError *error) + { + XCTFail("Test failure - %@", error); + [expectation fulfill]; + }; + + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; + [matrixSDKTestsData doMXSessionTestWithBob:nil + readyToTest:^(MXSession *bobSession, XCTestExpectation *expectation2) + { + + // Send a bunch of messages whilst changing room visibility + MXRoom *room = [aliceSession1 roomWithRoomId:roomId]; + [self sendMessage:@"Hello" room:room success:^{ + [self setHistoryVisibility:kMXRoomHistoryVisibilityJoined room:room success:^{ + [self sendMessage:@"Hi" room:room success:^{ + [self setHistoryVisibility:kMXRoomHistoryVisibilityShared room:room success:^{ + [self sendMessage:@"How are you?" room:room success:^{ + + // Alice signs in on a new device + [matrixSDKTestsE2EData loginUserOnANewDevice:self + credentials:aliceSession1.matrixRestClient.credentials + withPassword:MXTESTS_ALICE_PWD store:[[MXMemoryStore alloc] init] + onComplete:^(MXSession *aliceSession2) + { + + // Initially Alice2 has no keys + XCTAssertEqual([self numberOfKeysInSession:aliceSession2], 0); + + // Make each Alice device trust each other + [aliceSession1.crypto setDeviceVerification:MXDeviceVerified forDevice:aliceSession2.myDeviceId ofUser:aliceSession1.myUserId success:^{ + [aliceSession2.crypto setDeviceVerification:MXDeviceVerified forDevice:aliceSession1.myDeviceId ofUser:aliceSession1.myUserId success:^{ + + // Alice2 paginates in the room to get the keys forwarded to her + MXRoom *roomFromAlice2POV = [aliceSession2 roomWithRoomId:roomId]; + [roomFromAlice2POV liveTimeline:^(id liveTimeline) { + [liveTimeline resetPagination]; + [liveTimeline paginate:10 direction:MXTimelineDirectionBackwards onlyFromStore:NO complete:^{ + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + + // Alice2 now has all 3 keys, despite only two of them having shared history + XCTAssertEqual([self numberOfKeysInSession:aliceSession2], 3); + + // Now Alice2 invites Bob into the conversation + [roomFromAlice2POV inviteUser:bobSession.myUser.userId success:^{ + } failure:failureBlock]; + }); + } failure:failureBlock]; + }]; + } failure:failureBlock]; + } failure:failureBlock]; + }]; + } failure:failureBlock]; + } failure:failureBlock]; + } failure:failureBlock]; + } failure:failureBlock]; + } failure:failureBlock]; + + // Listen to a notification of to_device events, which will store keys on Bob's device + __block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionOnToDeviceEventNotification + object:bobSession + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notif) + { + + // Give some extra time, sa we are storing keys in Realm + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + if (observer) + { + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + observer = nil; + + // Bob should only have recieved 2 keys, as the third Alice's key has `sharedHistory` set to false + XCTAssertEqual([self numberOfKeysInSession:bobSession], 2); + [expectation fulfill]; + } + }); + }]; + }]; + }]; +} + +#pragma mark - Helpers + +/** + Get number of inbound keys stored in a session + */ +- (NSUInteger)numberOfKeysInSession:(MXSession *)session +{ + return [session.crypto.store inboundGroupSessions].count; +} + +/** + Send message and await its delivery + */ +- (void)sendMessage:(NSString *)message room:(MXRoom *)room success:(void(^)(void))success failure:(void(^)(NSError *error))failure +{ + __block id listener = [room listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage] + onEvent:^(MXEvent * _Nonnull event, MXTimelineDirection direction, MXRoomState * _Nullable roomState) + { + [room removeListener:listener]; + success(); + }]; + + [room sendTextMessage:message threadId:nil success:nil failure:failure]; +} + +/** + Set room visibility and awaits its processing + */ +- (void)setHistoryVisibility:(MXRoomHistoryVisibility)historyVisibility room:(MXRoom *)room success:(void(^)(void))success failure:(void(^)(NSError *error))failure +{ + __block id listener = [room listenToEventsOfTypes:@[kMXEventTypeStringRoomHistoryVisibility] + onEvent:^(MXEvent * _Nonnull event, MXTimelineDirection direction, MXRoomState * _Nullable roomState) + { + [room removeListener:listener]; + success(); + }]; + + [room setHistoryVisibility:historyVisibility success:nil failure:failure]; +} + @end #pragma clang diagnostic pop diff --git a/MatrixSDKTests/MatrixSDKTestsE2EData.h b/MatrixSDKTests/MatrixSDKTestsE2EData.h index ac3c1cbee7..982b2d302b 100644 --- a/MatrixSDKTests/MatrixSDKTestsE2EData.h +++ b/MatrixSDKTests/MatrixSDKTestsE2EData.h @@ -86,6 +86,12 @@ withPassword:(NSString*)password onComplete:(void (^)(MXSession *newSession))onComplete; +- (void)loginUserOnANewDevice:(XCTestCase*)testCase + credentials:(MXCredentials*)credentials + withPassword:(NSString*)password + store:(id)store + onComplete:(void (^)(MXSession *newSession))onComplete; + #pragma mark - Cross-signing diff --git a/MatrixSDKTests/MatrixSDKTestsE2EData.m b/MatrixSDKTests/MatrixSDKTestsE2EData.m index ff56321192..861e0f67c8 100644 --- a/MatrixSDKTests/MatrixSDKTestsE2EData.m +++ b/MatrixSDKTests/MatrixSDKTestsE2EData.m @@ -24,6 +24,7 @@ #import "MXDeviceListOperation.h" #import "MXFileStore.h" #import "MXNoStore.h" +#import "MXTools.h" @interface MatrixSDKTestsE2EData () @@ -292,6 +293,19 @@ - (void)loginUserOnANewDevice:(XCTestCase*)testCase credentials:(MXCredentials*)credentials withPassword:(NSString*)password onComplete:(void (^)(MXSession *newSession))onComplete +{ + [self loginUserOnANewDevice:testCase + credentials:credentials + withPassword:password + store:[[MXNoStore alloc] init] + onComplete:onComplete]; +} + +- (void)loginUserOnANewDevice:(XCTestCase*)testCase + credentials:(MXCredentials*)credentials + withPassword:(NSString*)password + store:(id)store + onComplete:(void (^)(MXSession *newSession))onComplete { [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; @@ -307,13 +321,19 @@ - (void)loginUserOnANewDevice:(XCTestCase*)testCase MXSession *newSession = [[MXSession alloc] initWithMatrixRestClient:mxRestClient2]; [matrixSDKTestsData retain:newSession]; - [newSession start:^{ - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; - - onComplete(newSession); - + MXWeakify(newSession); + [newSession setStore:store success:^{ + MXStrongifyAndReturnIfNil(newSession); + [newSession start:^{ + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; + + onComplete(newSession); + + } failure:^(NSError *error) { + [matrixSDKTestsData breakTestCase:testCase reason:@"Cannot set up intial test conditions - error: %@", error]; + }]; } failure:^(NSError *error) { - [matrixSDKTestsData breakTestCase:testCase reason:@"Cannot set up intial test conditions - error: %@", error]; + [matrixSDKTestsData breakTestCase:testCase reason:@"Cannot set up store - error: %@", error]; }]; } failure:^(NSError *error) {