diff --git a/CHANGES.rst b/CHANGES.rst index 813387b49c..3a7af474ec 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,13 +1,14 @@ Changes in Matrix iOS SDK in 0.12.1 (2012-12-xx) =============================================== +Improvements: +* MXCrypto: Use the last olm session that got a message (vector-im/riot-ios/issues/2128). +* MXScanManager: Support the encrypted body (the request body is now encrypted by default using the server public key). +* MXMediaManager: Support the encrypted body. + Bug Fix: * MXCryptoStore: Stop duplicating devices in the store (vector-im/riot-ios/issues/2132). -Improvements: - * MXScanManager: Support the encrypted body (the request body is now encrypted by default using the server public key). - * MXMediaManager: Support the encrypted body. - Changes in Matrix iOS SDK in 0.12.0 (2018-12-06) =============================================== diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 38502bf6eb..a725636f3b 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -240,6 +240,8 @@ 32E226A61D06AC9F00E6CA54 /* MXPeekingRoom.h in Headers */ = {isa = PBXBuildFile; fileRef = 32E226A41D06AC9F00E6CA54 /* MXPeekingRoom.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32E226A71D06AC9F00E6CA54 /* MXPeekingRoom.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E226A51D06AC9F00E6CA54 /* MXPeekingRoom.m */; }; 32E226A91D081CE200E6CA54 /* MXPeekingRoomTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E226A81D081CE200E6CA54 /* MXPeekingRoomTests.m */; }; + 32E402B921C957D2004E87A6 /* MXOlmSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 32E402B721C957D2004E87A6 /* MXOlmSession.h */; }; + 32E402BA21C957D2004E87A6 /* MXOlmSession.m in Sources */ = {isa = PBXBuildFile; fileRef = 32E402B821C957D2004E87A6 /* MXOlmSession.m */; }; 32F634AB1FC5E3480054EF49 /* MXEventDecryptionResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 32F634A91FC5E3470054EF49 /* MXEventDecryptionResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32F634AC1FC5E3480054EF49 /* MXEventDecryptionResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F634AA1FC5E3470054EF49 /* MXEventDecryptionResult.m */; }; 32F945F51FAB83D900622468 /* MXIncomingRoomKeyRequestCancellation.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F945F11FAB83D800622468 /* MXIncomingRoomKeyRequestCancellation.m */; }; @@ -614,6 +616,8 @@ 32E226A41D06AC9F00E6CA54 /* MXPeekingRoom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXPeekingRoom.h; sourceTree = ""; }; 32E226A51D06AC9F00E6CA54 /* MXPeekingRoom.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXPeekingRoom.m; sourceTree = ""; }; 32E226A81D081CE200E6CA54 /* MXPeekingRoomTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXPeekingRoomTests.m; sourceTree = ""; }; + 32E402B721C957D2004E87A6 /* MXOlmSession.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXOlmSession.h; sourceTree = ""; }; + 32E402B821C957D2004E87A6 /* MXOlmSession.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXOlmSession.m; sourceTree = ""; }; 32F1FE9AF82A426C2EAED587 /* Pods-MatrixSDK.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatrixSDK.release.xcconfig"; path = "Pods/Target Support Files/Pods-MatrixSDK/Pods-MatrixSDK.release.xcconfig"; sourceTree = ""; }; 32F634A91FC5E3470054EF49 /* MXEventDecryptionResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXEventDecryptionResult.h; sourceTree = ""; }; 32F634AA1FC5E3470054EF49 /* MXEventDecryptionResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXEventDecryptionResult.m; sourceTree = ""; }; @@ -1131,6 +1135,8 @@ 32A151451DAF7C0C00400192 /* MXUsersDevicesMap.m */, 324BE46A1E422766008D99D4 /* MXMegolmSessionData.h */, 324BE46B1E422766008D99D4 /* MXMegolmSessionData.m */, + 32E402B721C957D2004E87A6 /* MXOlmSession.h */, + 32E402B821C957D2004E87A6 /* MXOlmSession.m */, ); path = Data; sourceTree = ""; @@ -1593,6 +1599,7 @@ 323F3F9420D3F0C700D26D6A /* MXRoomEventFilter.h in Headers */, 32A9E8251EF4026E0081358A /* MXUIKitBackgroundModeHandler.h in Headers */, 325D1C261DFECE0D0070B8BF /* MXCrypto_Private.h in Headers */, + 32E402B921C957D2004E87A6 /* MXOlmSession.h in Headers */, B146D4D621A5A44E00D8C2C6 /* MXScanRealmInMemoryProvider.h in Headers */, B17285792100C8EA0052C51E /* MXSendReplyEventStringsLocalizable.h in Headers */, 32A151521DAF8A7200400192 /* MXQueuedEncryption.h in Headers */, @@ -1866,6 +1873,7 @@ 32D776821A27877300FC4AA2 /* MXMemoryRoomStore.m in Sources */, 3293C701214BBA4F009B3DDB /* MXPeekingRoomSummary.m in Sources */, C602B58C1F2268F700B67D87 /* MXRoom.swift in Sources */, + 32E402BA21C957D2004E87A6 /* MXOlmSession.m in Sources */, 32A151271DABB0CB00400192 /* MXMegolmDecryption.m in Sources */, C6D5D60E1E4FBD2900706C0F /* MXSession.swift in Sources */, 320DFDE319DD99B60068622A /* MXError.m in Sources */, diff --git a/MatrixSDK/Crypto/Data/MXOlmSession.h b/MatrixSDK/Crypto/Data/MXOlmSession.h new file mode 100644 index 0000000000..80ba7b1535 --- /dev/null +++ b/MatrixSDK/Crypto/Data/MXOlmSession.h @@ -0,0 +1,56 @@ +/* + Copyright 2018 New Vector Ltd + + 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 + +#import "MXSDKOptions.h" + +#ifdef MX_CRYPTO + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + The 'MXOlmSession' class adds additional information to an OLMSession object from OLMKit. + */ +@interface MXOlmSession : NSObject + + +- (instancetype)initWithOlmSession:(OLMSession*)session; + +/** + The associated olm session. + */ +@property (nonatomic, readonly) OLMSession *session; + +/** + Timestamp at which the session last received a message. + */ +@property (nonatomic) NSTimeInterval lastReceivedMessageTs; + + +/** + Notify this model that a message has been received on this olm session + so that it updates `lastReceivedMessageTs` + */ +- (void)didReceiveMessage; + +@end + +NS_ASSUME_NONNULL_END + +#endif diff --git a/MatrixSDK/Crypto/Data/MXOlmSession.m b/MatrixSDK/Crypto/Data/MXOlmSession.m new file mode 100644 index 0000000000..5dea2c50bb --- /dev/null +++ b/MatrixSDK/Crypto/Data/MXOlmSession.m @@ -0,0 +1,37 @@ +/* + Copyright 2018 New Vector Ltd + + 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 "MXOlmSession.h" + +@implementation MXOlmSession + +- (instancetype)initWithOlmSession:(OLMSession *)session +{ + self = [super init]; + if (self) + { + _session = session; + _lastReceivedMessageTs = 0; + } + return self; +} + +- (void)didReceiveMessage +{ + _lastReceivedMessageTs = [[NSDate date] timeIntervalSince1970]; +} + +@end diff --git a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h index 387214bec1..f17360451a 100644 --- a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h +++ b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h @@ -24,6 +24,7 @@ #import "MXJSONModels.h" #import +#import "MXOlmSession.h" #import "MXOlmInboundGroupSession.h" #import "MXDeviceInfo.h" #import "MXOutgoingRoomKeyRequest.h" @@ -188,16 +189,26 @@ @param deviceKey the public key of the other device. @param session the end-to-end session. */ -- (void)storeSession:(OLMSession*)session forDevice:(NSString*)deviceKey; +- (void)storeSession:(MXOlmSession*)session forDevice:(NSString*)deviceKey; /** - Retrieve the end-to-end sessions between the logged-in user and another + Retrieve an end-to-end session between the logged-in user and another device. @param deviceKey the public key of the other device. - @return {object} A map from sessionId to Base64 end-to-end session. + @return a array of end-to-end sessions sorted by the last updated first. */ -- (NSDictionary*)sessionsWithDevice:(NSString*)deviceKey; +- (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*)sessionId; + +/** + Retrieve all end-to-end sessions between the logged-in user and another + device sorted by `lastReceivedMessageTs`, the most recent(higest value) first. + + @param deviceKey the public key of the other device. + @return a array of end-to-end sessions. + */ +- (NSArray*)sessionsWithDevice:(NSString*)deviceKey; + /** Store an inbound group session. diff --git a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m index 1593ca99a4..4573ff5c95 100644 --- a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m +++ b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m @@ -23,7 +23,7 @@ #import "MXSession.h" #import "MXTools.h" -NSUInteger const kMXRealmCryptoStoreVersion = 8; +NSUInteger const kMXRealmCryptoStoreVersion = 9; static NSString *const kMXRealmCryptoStoreFolder = @"MXRealmCryptoStore"; @@ -84,6 +84,7 @@ + (NSString *)primaryKey @interface MXRealmOlmSession : RLMObject @property NSString *sessionId; @property NSString *deviceKey; +@property NSTimeInterval lastReceivedMessageTs; @property NSData *olmSessionData; @end @@ -599,7 +600,7 @@ - (MXRealmRoomAlgorithm *)realmRoomAlgorithmForRoom:(NSString*)roomId inRealm:(R } -- (void)storeSession:(OLMSession*)session forDevice:(NSString*)deviceKey +- (void)storeSession:(MXOlmSession*)session forDevice:(NSString*)deviceKey { BOOL isNew = NO; NSDate *startDate = [NSDate date]; @@ -607,20 +608,22 @@ - (void)storeSession:(OLMSession*)session forDevice:(NSString*)deviceKey RLMRealm *realm = self.realm; [realm transactionWithBlock:^{ - MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:realm where:@"sessionId = %@ AND deviceKey = %@", session.sessionIdentifier, deviceKey].firstObject; + MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:realm where:@"sessionId = %@ AND deviceKey = %@", session.session.sessionIdentifier, deviceKey].firstObject; if (realmOlmSession) { // Update the existing one - realmOlmSession.olmSessionData = [NSKeyedArchiver archivedDataWithRootObject:session]; + realmOlmSession.olmSessionData = [NSKeyedArchiver archivedDataWithRootObject:session.session]; } else { // Create it realmOlmSession = [[MXRealmOlmSession alloc] initWithValue:@{ - @"sessionId": session.sessionIdentifier, + @"sessionId": session.session.sessionIdentifier, @"deviceKey": deviceKey, - @"olmSessionData": [NSKeyedArchiver archivedDataWithRootObject:session] + @"olmSessionData": [NSKeyedArchiver archivedDataWithRootObject:session.session] }]; + realmOlmSession.lastReceivedMessageTs = session.lastReceivedMessageTs; + [realm addObject:realmOlmSession]; } }]; @@ -628,19 +631,46 @@ - (void)storeSession:(OLMSession*)session forDevice:(NSString*)deviceKey NSLog(@"[MXRealmCryptoStore] storeSession (%@) in %.0fms", (isNew?@"NEW":@"UPDATE"), [[NSDate date] timeIntervalSinceDate:startDate] * 1000); } -- (NSDictionary*)sessionsWithDevice:(NSString*)deviceKey +- (MXOlmSession*)sessionWithDevice:(NSString*)deviceKey andSessionId:(NSString*)sessionId { - NSMutableDictionary *sessionsWithDevice; + MXRealmOlmSession *realmOlmSession = [MXRealmOlmSession objectsInRealm:self.realm + where:@"sessionId = %@ AND deviceKey = %@", sessionId, deviceKey].firstObject; + + MXOlmSession *mxOlmSession; + if (realmOlmSession.olmSessionData) + { + OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; - RLMResults *realmOlmSessions = [MXRealmOlmSession objectsInRealm:self.realm where:@"deviceKey = %@", deviceKey]; + mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; + } + + return mxOlmSession; +} + +- (NSArray*)sessionsWithDevice:(NSString*)deviceKey; +{ + NSMutableArray *sessionsWithDevice; + + RLMResults *realmOlmSessions = [[MXRealmOlmSession objectsInRealm:self.realm + where:@"deviceKey = %@", deviceKey] + sortedResultsUsingKeyPath:@"lastReceivedMessageTs" ascending:NO]; for (MXRealmOlmSession *realmOlmSession in realmOlmSessions) { if (!sessionsWithDevice) { - sessionsWithDevice = [NSMutableDictionary dictionary]; + sessionsWithDevice = [NSMutableArray array]; } - sessionsWithDevice[realmOlmSession.sessionId] = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; + if (realmOlmSession.olmSessionData) + { + OLMSession *olmSession = [NSKeyedUnarchiver unarchiveObjectWithData:realmOlmSession.olmSessionData]; + + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + mxOlmSession.lastReceivedMessageTs = realmOlmSession.lastReceivedMessageTs; + + [sessionsWithDevice addObject:mxOlmSession]; + } } return sessionsWithDevice; @@ -1189,6 +1219,23 @@ + (RLMRealm*)realmForUser:(NSString*)userId // make queries. So, the cleaning will be done afterwards. cleanDuplicatedDevices = YES; } + + case 8: + { + // MXRealmOlmSession.lastReceivedMessageTs has been added to implement: + // Use the last olm session that got a message + // https://github.com/vector-im/riot-ios/issues/2128 + + NSLog(@"[MXRealmCryptoStore] Migration from schema #8 -> #9"); + + NSLog(@" Add lastReceivedMessageTs = 0 to all MXRealmOlmSession objects"); + [migration enumerateObjects:MXRealmOlmSession.className block:^(RLMObject *oldObject, RLMObject *newObject) { + + newObject[@"lastReceivedMessageTs"] = 0; + }]; + + NSLog(@"[MXRealmCryptoStore] Migration from schema #8 -> #9 completed"); + } } } }; diff --git a/MatrixSDK/Crypto/MXOlmDevice.m b/MatrixSDK/Crypto/MXOlmDevice.m index d956182dc1..82cb66007f 100644 --- a/MatrixSDK/Crypto/MXOlmDevice.m +++ b/MatrixSDK/Crypto/MXOlmDevice.m @@ -148,7 +148,14 @@ - (NSString *)createOutboundSession:(NSString *)theirIdentityKey theirOneTimeKey if (olmSession) { - [store storeSession:olmSession forDevice:theirIdentityKey]; + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + + // Pretend we've received a message at this point, otherwise + // if we try to send a message to the device, it won't use + // this session + [mxOlmSession didReceiveMessage]; + + [store storeSession:mxOlmSession forDevice:theirIdentityKey]; return olmSession.sessionIdentifier; } else if (error) @@ -184,7 +191,13 @@ - (NSString*)createInboundSession:(NSString*)theirDeviceIdentityKey messageType: NSLog(@"[MXOlmDevice] createInboundSession. decryptMessage error: %@", error); } - [store storeSession:olmSession forDevice:theirDeviceIdentityKey]; + MXOlmSession *mxOlmSession = [[MXOlmSession alloc] initWithOlmSession:olmSession]; + + // This counts as a received message: set last received message time + // to now + [mxOlmSession didReceiveMessage]; + + [store storeSession:mxOlmSession forDevice:theirDeviceIdentityKey]; return olmSession.sessionIdentifier; } @@ -198,24 +211,22 @@ - (NSString*)createInboundSession:(NSString*)theirDeviceIdentityKey messageType: - (NSArray *)sessionIdsForDevice:(NSString *)theirDeviceIdentityKey { - NSDictionary *sessions = [store sessionsWithDevice:theirDeviceIdentityKey]; + NSArray *sessions = [store sessionsWithDevice:theirDeviceIdentityKey]; + + NSMutableArray *sessionIds = [NSMutableArray arrayWithCapacity:sessions.count]; + for (MXOlmSession *session in sessions) + { + [sessionIds addObject:session.session.sessionIdentifier]; + } - return sessions.allKeys; + return sessionIds; } - (NSString *)sessionIdForDevice:(NSString *)theirDeviceIdentityKey { - NSString *sessionId; - - NSArray *sessionIds = [self sessionIdsForDevice:theirDeviceIdentityKey]; - if (sessionIds.count) - { - // Use the session with the lowest ID. - NSArray *sortedSessionIds = [sessionIds sortedArrayUsingSelector:@selector(compare:)]; - sessionId = sortedSessionIds[0]; - } - - return sessionId; + // Use the session that has most recently received a message + // This is the first item in the sorted array returned by the store + return [store sessionsWithDevice:theirDeviceIdentityKey].firstObject.session.sessionIdentifier; } - (NSDictionary *)encryptMessage:(NSString *)theirDeviceIdentityKey sessionId:(NSString *)sessionId payloadString:(NSString *)payloadString @@ -223,21 +234,21 @@ - (NSDictionary *)encryptMessage:(NSString *)theirDeviceIdentityKey sessionId:(N NSError *error; OLMMessage *olmMessage; - OLMSession *olmSession = [self sessionForDevice:theirDeviceIdentityKey andSessionId:sessionId]; + MXOlmSession *mxOlmSession = [self sessionForDevice:theirDeviceIdentityKey andSessionId:sessionId]; // NSLog(@">>>> encryptMessage: olmSession.sessionIdentifier: %@", olmSession.sessionIdentifier); // NSLog(@">>>> payloadString: %@", payloadString); - if (olmSession) + if (mxOlmSession.session) { - olmMessage = [olmSession encryptMessage:payloadString error:&error]; + olmMessage = [mxOlmSession.session encryptMessage:payloadString error:&error]; if (error) { NSLog(@"[MXOlmDevice] encryptMessage failed: %@", error); } - [store storeSession:olmSession forDevice:theirDeviceIdentityKey]; + [store storeSession:mxOlmSession forDevice:theirDeviceIdentityKey]; } //NSLog(@">>>> ciphertext: %@", olmMessage.ciphertext); @@ -254,17 +265,18 @@ - (NSString*)decryptMessage:(NSString*)ciphertext withType:(NSUInteger)messageTy NSError *error; NSString *payloadString; - OLMSession *olmSession = [self sessionForDevice:theirDeviceIdentityKey andSessionId:sessionId]; - if (olmSession) + MXOlmSession *mxOlmSession = [self sessionForDevice:theirDeviceIdentityKey andSessionId:sessionId]; + if (mxOlmSession) { - payloadString = [olmSession decryptMessage:[[OLMMessage alloc] initWithCiphertext:ciphertext type:messageType] error:&error]; + payloadString = [mxOlmSession.session decryptMessage:[[OLMMessage alloc] initWithCiphertext:ciphertext type:messageType] error:&error]; if (error) { NSLog(@"[MXOlmDevice] decryptMessage failed: %@", error); } - [store storeSession:olmSession forDevice:theirDeviceIdentityKey]; + [mxOlmSession didReceiveMessage]; + [store storeSession:mxOlmSession forDevice:theirDeviceIdentityKey]; } return payloadString; @@ -277,8 +289,8 @@ - (BOOL)matchesSession:(NSString *)theirDeviceIdentityKey sessionId:(NSString *) return NO; } - OLMSession *olmSession = [self sessionForDevice:theirDeviceIdentityKey andSessionId:sessionId]; - return [olmSession matchesInboundSession:ciphertext]; + MXOlmSession *mxOlmSession = [self sessionForDevice:theirDeviceIdentityKey andSessionId:sessionId]; + return [mxOlmSession.session matchesInboundSession:ciphertext]; } @@ -559,9 +571,9 @@ - (NSString *)sha256:(NSString *)message #pragma mark - Private methods -- (OLMSession*)sessionForDevice:(NSString *)theirDeviceIdentityKey andSessionId:(NSString*)sessionId +- (MXOlmSession*)sessionForDevice:(NSString *)theirDeviceIdentityKey andSessionId:(NSString*)sessionId { - return [store sessionsWithDevice:theirDeviceIdentityKey][sessionId]; + return [store sessionWithDevice:theirDeviceIdentityKey andSessionId:sessionId]; } /**