From 731e4be0a83f8abcb0ae1e4b9681ed831944d7c2 Mon Sep 17 00:00:00 2001 From: gulekismail Date: Wed, 7 Sep 2022 13:43:23 +0300 Subject: [PATCH 01/30] Prepare for new sprint From 7c43bb0dc73a248aa4e445d4c84071afc5d54b29 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 16 Sep 2022 09:35:55 +0100 Subject: [PATCH 02/30] Enable group session cache by default --- MatrixSDK/MXSDKOptions.h | 2 +- MatrixSDK/MXSDKOptions.m | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/MatrixSDK/MXSDKOptions.h b/MatrixSDK/MXSDKOptions.h index ce604e4f5b..a9877721f1 100644 --- a/MatrixSDK/MXSDKOptions.h +++ b/MatrixSDK/MXSDKOptions.h @@ -220,7 +220,7 @@ NS_ASSUME_NONNULL_BEGIN Enable performance optimization where inbound group sessions are cached between decryption of events rather than fetched from the store every time. - @remark By default, the value is set randomly between YES / NO to perform a very basic A/B test + @remark YES by default */ @property (nonatomic) BOOL enableGroupSessionCache; diff --git a/MatrixSDK/MXSDKOptions.m b/MatrixSDK/MXSDKOptions.m index 6c3c712905..6138216ce5 100644 --- a/MatrixSDK/MXSDKOptions.m +++ b/MatrixSDK/MXSDKOptions.m @@ -59,10 +59,7 @@ - (instancetype)init _enableCryptoV2 = NO; #endif - // The value is set randomly between YES / NO to perform a very basic A/B test - // measured by `analytics` (if set and enabled) - _enableGroupSessionCache = arc4random_uniform(2) == 1; - + _enableGroupSessionCache = YES; _enableSymmetricBackup = NO; } From 354b35e398ce0deb1de9ad08240628e1f7889104 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 16 Sep 2022 09:39:51 +0100 Subject: [PATCH 03/30] Changelog --- changelog.d/pr-1575.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-1575.change diff --git a/changelog.d/pr-1575.change b/changelog.d/pr-1575.change new file mode 100644 index 0000000000..2f8ccbe89c --- /dev/null +++ b/changelog.d/pr-1575.change @@ -0,0 +1 @@ +Crypto: Enable group session cache by default From 3f0584c60fb6445d8ad6d73dc8f40db154fcabef Mon Sep 17 00:00:00 2001 From: aringenbach Date: Mon, 5 Sep 2022 11:00:43 +0200 Subject: [PATCH 04/30] Remove partialTextMessage support --- MatrixSDK/Background/MXBackgroundStore.swift | 9 ------- MatrixSDK/Data/MXRoom.h | 9 ------- MatrixSDK/Data/MXRoom.m | 11 -------- .../Data/Store/MXFileStore/MXFileRoomStore.m | 6 ----- .../Data/Store/MXFileStore/MXFileStore.m | 10 ------- .../Store/MXMemoryStore/MXMemoryRoomStore.h | 7 ----- .../Data/Store/MXMemoryStore/MXMemoryStore.m | 12 --------- MatrixSDK/Data/Store/MXNoStore/MXNoStore.m | 27 ------------------- MatrixSDK/Data/Store/MXStore.h | 21 --------------- changelog.d/6670.change | 1 + 10 files changed, 1 insertion(+), 112 deletions(-) create mode 100644 changelog.d/6670.change diff --git a/MatrixSDK/Background/MXBackgroundStore.swift b/MatrixSDK/Background/MXBackgroundStore.swift index 57826d6906..efac7b8ead 100644 --- a/MatrixSDK/Background/MXBackgroundStore.swift +++ b/MatrixSDK/Background/MXBackgroundStore.swift @@ -201,15 +201,6 @@ class MXBackgroundStore: NSObject, MXStore { func deleteGroup(_ groupId: String) { } - @available(*, deprecated, message: "use storePartialAttributedTextMessage") - func storePartialTextMessage(forRoom roomId: String, partialTextMessage: String) { - } - - @available(*, deprecated, message: "use partialAttributedTextMessage") - func partialTextMessage(ofRoom roomId: String) -> String? { - return nil - } - func storePartialAttributedTextMessage(forRoom roomId: String, partialAttributedTextMessage: NSAttributedString) { } diff --git a/MatrixSDK/Data/MXRoom.h b/MatrixSDK/Data/MXRoom.h index e7b31172a9..0b5a3caad8 100644 --- a/MatrixSDK/Data/MXRoom.h +++ b/MatrixSDK/Data/MXRoom.h @@ -160,15 +160,6 @@ FOUNDATION_EXPORT NSInteger const kMXRoomInvalidInviteSenderErrorCode; success:(void (^)(void))success failure:(void (^)(NSError *))failure NS_REFINED_FOR_SWIFT; -/** - The text message partially typed by the user but not yet sent. - The value is stored by the session store. Thus, it can be retrieved - when the application restarts. - - @deprecated use partialAttributedTextMessage - */ -@property (nonatomic) NSString *partialTextMessage __deprecated_msg("use partialAttributedTextMessage"); - /** The text message partially typed by the user but not yet sent. The value is stored by the session store. Thus, it can be retrieved diff --git a/MatrixSDK/Data/MXRoom.m b/MatrixSDK/Data/MXRoom.m index 718dea2166..a107c3f84c 100644 --- a/MatrixSDK/Data/MXRoom.m +++ b/MatrixSDK/Data/MXRoom.m @@ -381,17 +381,6 @@ - (MXHTTPOperation*)members:(void (^)(MXRoomMembers *members))success return operation; } - -- (void)setPartialTextMessage:(NSString *)partialTextMessage -{ - [mxSession.store storePartialTextMessageForRoom:self.roomId partialTextMessage:partialTextMessage]; -} - -- (NSString *)partialTextMessage -{ - return [mxSession.store partialTextMessageOfRoom:self.roomId]; -} - - (void)setPartialAttributedTextMessage:(NSAttributedString *)partialAttributedTextMessage { [mxSession.store storePartialAttributedTextMessageForRoom:self.roomId partialAttributedTextMessage:partialAttributedTextMessage]; diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m b/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m index 4ce33ed88a..a673efa849 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileRoomStore.m @@ -32,8 +32,6 @@ - (id)initWithCoder:(NSCoder *)aDecoder self.hasReachedHomeServerPaginationEnd = [aDecoder decodeBoolForKey:@"hasReachedHomeServerPaginationEnd"]; self.hasLoadedAllRoomMembersForRoom = [aDecoder decodeBoolForKey:@"hasLoadedAllRoomMembersForRoom"]; - self.partialTextMessage = [aDecoder decodeObjectForKey:@"partialTextMessage"]; - self.partialAttributedTextMessage = [aDecoder decodeObjectForKey:@"partialAttributedTextMessage"]; // Rebuild the messagesByEventIds cache @@ -67,10 +65,6 @@ - (void)encodeWithCoder:(NSCoder *)aCoder [aCoder encodeBool:self.hasReachedHomeServerPaginationEnd forKey:@"hasReachedHomeServerPaginationEnd"]; [aCoder encodeBool:self.hasLoadedAllRoomMembersForRoom forKey:@"hasLoadedAllRoomMembersForRoom"]; - if (self.partialTextMessage) - { - [aCoder encodeObject:self.partialTextMessage forKey:@"partialTextMessage"]; - } if (self.partialAttributedTextMessage) { [aCoder encodeObject:self.partialAttributedTextMessage forKey:@"partialAttributedTextMessage"]; diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m index 219718c488..575027de42 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m @@ -440,16 +440,6 @@ - (void)storeHasReachedHomeServerPaginationEndForRoom:(NSString *)roomId andValu } } -- (void)storePartialTextMessageForRoom:(NSString *)roomId partialTextMessage:(NSString *)partialTextMessage -{ - [super storePartialTextMessageForRoom:roomId partialTextMessage:partialTextMessage]; - - if (NSNotFound == [roomsToCommitForMessages indexOfObject:roomId]) - { - [roomsToCommitForMessages addObject:roomId]; - } -} - - (void)storePartialAttributedTextMessageForRoom:(NSString *)roomId partialAttributedTextMessage:(NSAttributedString *)partialAttributedTextMessage { [super storePartialAttributedTextMessageForRoom:roomId partialAttributedTextMessage:partialAttributedTextMessage]; diff --git a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h index e4e738a74d..d5a5196302 100644 --- a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h +++ b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryRoomStore.h @@ -111,13 +111,6 @@ */ - (NSArray*)relationsForEvent:(NSString*)eventId relationType:(NSString*)relationType; -/** - The text message partially typed by the user but not yet sent in the room. - - @deprecated use partialAttributedTextMessage - */ -@property (nonatomic) NSString *partialTextMessage __deprecated_msg("use partialAttributedTextMessage"); - /** The text message partially typed by the user but not yet sent in the room. */ diff --git a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m index 1ad1146480..7ec517631a 100644 --- a/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m +++ b/MatrixSDK/Data/Store/MXMemoryStore/MXMemoryStore.m @@ -171,18 +171,6 @@ - (BOOL)hasLoadedAllRoomMembersForRoom:(NSString *)roomId return [roomStore enumeratorForMessagesWithTypeIn:types]; } -- (void)storePartialTextMessageForRoom:(NSString *)roomId partialTextMessage:(NSString *)partialTextMessage -{ - MXMemoryRoomStore *roomStore = [self getOrCreateRoomStore:roomId]; - roomStore.partialTextMessage = partialTextMessage; -} - -- (NSString *)partialTextMessageOfRoom:(NSString *)roomId -{ - MXMemoryRoomStore *roomStore = [self getOrCreateRoomStore:roomId]; - return roomStore.partialTextMessage; -} - - (void)storePartialAttributedTextMessageForRoom:(NSString *)roomId partialAttributedTextMessage:(NSAttributedString *)partialAttributedTextMessage { MXMemoryRoomStore *roomStore = [self getOrCreateRoomStore:roomId]; diff --git a/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m b/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m index 7fdde37915..dcb868b5c8 100644 --- a/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m +++ b/MatrixSDK/Data/Store/MXNoStore/MXNoStore.m @@ -39,9 +39,6 @@ @interface MXNoStore () // key: roomId, value: the last message of this room NSMutableDictionary *lastMessages; - // key: roomId, value: the text message the user typed - NSMutableDictionary *partialTextMessages; - // key: roomId, value: the text message the user typed NSMutableDictionary *partialAttributedTextMessages; @@ -74,7 +71,6 @@ - (instancetype)init hasReachedHomeServerPaginations = [NSMutableDictionary dictionary]; hasLoadedAllRoomMembersForRooms = [NSMutableDictionary dictionary]; lastMessages = [NSMutableDictionary dictionary]; - partialTextMessages = [NSMutableDictionary dictionary]; partialAttributedTextMessages = [NSMutableDictionary dictionary]; users = [NSMutableDictionary dictionary]; groups = [NSMutableDictionary dictionary]; @@ -168,10 +164,6 @@ - (void)deleteRoom:(NSString *)roomId { [lastMessages removeObjectForKey:roomId]; } - if (partialTextMessages[roomId]) - { - [partialTextMessages removeObjectForKey:roomId]; - } if (partialAttributedTextMessages[roomId]) { [partialAttributedTextMessages removeObjectForKey:roomId]; @@ -187,7 +179,6 @@ - (void)deleteAllData [hasReachedHomeServerPaginations removeAllObjects]; [hasLoadedAllRoomMembersForRooms removeAllObjects]; [lastMessages removeAllObjects]; - [partialTextMessages removeAllObjects]; [partialAttributedTextMessages removeAllObjects]; [roomSummaryStore removeAllSummaries]; } @@ -381,23 +372,6 @@ - (void)filterIdForFilter:(nonnull MXFilterJSONModel*)filter success(nil); } -- (void)storePartialTextMessageForRoom:(NSString *)roomId partialTextMessage:(NSString *)partialTextMessage -{ - if (partialTextMessage) - { - partialTextMessages[roomId] = partialTextMessage; - } - else - { - [partialTextMessages removeObjectForKey:roomId]; - } -} - -- (NSString *)partialTextMessageOfRoom:(NSString *)roomId -{ - return partialTextMessages[roomId]; -} - - (void)storePartialAttributedTextMessageForRoom:(NSString *)roomId partialAttributedTextMessage:(NSAttributedString *)partialAttributedTextMessage { if (partialAttributedTextMessage) @@ -497,7 +471,6 @@ - (void)close [highlightCounts removeAllObjects]; [hasReachedHomeServerPaginations removeAllObjects]; [lastMessages removeAllObjects]; - [partialTextMessages removeAllObjects]; [partialAttributedTextMessages removeAllObjects]; [users removeAllObjects]; [groups removeAllObjects]; diff --git a/MatrixSDK/Data/Store/MXStore.h b/MatrixSDK/Data/Store/MXStore.h index 30cfe50b78..57ef3bedd3 100644 --- a/MatrixSDK/Data/Store/MXStore.h +++ b/MatrixSDK/Data/Store/MXStore.h @@ -227,27 +227,6 @@ - (void)deleteGroup:(nonnull NSString*)groupId; #pragma mark - -/** - Store the text message partially typed by the user but not yet sent. - - @deprecated use storePartialAttributedTextMessageForRoom - - @param roomId the id of the room. - @param partialTextMessage the text to store. Nil to reset it. - */ -- (void)storePartialTextMessageForRoom:(nonnull NSString*)roomId - partialTextMessage:(nonnull NSString*)partialTextMessage __deprecated_msg("use storePartialAttributedTextMessageForRoom"); - -/** - The text message typed by the user but not yet sent. - - @deprecated use partialAttributedTextMessageOfRoom - - @param roomId the id of the room. - @return the text message. Can be nil. - */ -- (NSString* _Nullable)partialTextMessageOfRoom:(nonnull NSString*)roomId __deprecated_msg("use partialAttributedTextMessageOfRoom"); - /** Store the text message partially typed by the user but not yet sent. diff --git a/changelog.d/6670.change b/changelog.d/6670.change new file mode 100644 index 0000000000..75e436eddf --- /dev/null +++ b/changelog.d/6670.change @@ -0,0 +1 @@ +Remove MXRoom's partialTextMessage support From f19c97d8a90116233c60205190a44ebebeede733 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Wed, 21 Sep 2022 09:32:41 +0200 Subject: [PATCH 05/30] Avoid main thread assertion if we can't get the application --- MatrixSDK/Utils/MXUIKitApplicationStateService.swift | 9 ++++++--- changelog.d/6754.misc | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog.d/6754.misc diff --git a/MatrixSDK/Utils/MXUIKitApplicationStateService.swift b/MatrixSDK/Utils/MXUIKitApplicationStateService.swift index a50264b325..70b42553f0 100644 --- a/MatrixSDK/Utils/MXUIKitApplicationStateService.swift +++ b/MatrixSDK/Utils/MXUIKitApplicationStateService.swift @@ -66,8 +66,6 @@ public class MXUIKitApplicationStateService: NSObject { applicationState = .inactive super.init() - // The service must be created on the main tread - assert(Thread.isMainThread, "[MXUIKitApplicationStateService] initialized on non-main thread.") applicationState = self.sharedApplicationState registerApplicationStateChangeNotifications() @@ -114,8 +112,13 @@ public class MXUIKitApplicationStateService: NSObject { private var sharedApplicationState: UIApplication.State { get { + guard let application = self.sharedApplication else { + return .inactive + } + // Can be only called from the main thread - self.sharedApplication?.applicationState ?? .inactive + assert(Thread.isMainThread, "[MXUIKitApplicationStateService] UIApplication.applicationState called on non-main thread.") + return application.applicationState } } } diff --git a/changelog.d/6754.misc b/changelog.d/6754.misc new file mode 100644 index 0000000000..2371dd3455 --- /dev/null +++ b/changelog.d/6754.misc @@ -0,0 +1 @@ +Avoid main thread assertion if we can't get the application From f2c3852bf5c620c5da63c3ddd40510d3763af36a Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Wed, 21 Sep 2022 08:58:08 +0100 Subject: [PATCH 06/30] Fix tests --- MatrixSDKTests/MXCryptoTests.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MatrixSDKTests/MXCryptoTests.m b/MatrixSDKTests/MXCryptoTests.m index 857c392186..244dffb5ba 100644 --- a/MatrixSDKTests/MXCryptoTests.m +++ b/MatrixSDKTests/MXCryptoTests.m @@ -31,6 +31,7 @@ #import "MXSendReplyEventDefaultStringLocalizer.h" #import "MXOutboundSessionInfo.h" #import +#import "MXLRUCache.h" #import "MXKey.h" @@ -2081,6 +2082,8 @@ - (void)testLateRoomKey id bobCryptoStore = (id)[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]; From dfcbe700deb1704d308cf4b7b300f0f7c2025371 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 15 Sep 2022 15:51:04 +0100 Subject: [PATCH 07/30] Upgrade minimum ios and osx deployment target --- MatrixSDK.podspec | 10 +- MatrixSDK.xcodeproj/project.pbxproj | 14 +-- MatrixSDK/Contrib/Swift/Data/MXRoom.swift | 1 - MatrixSDK/Contrib/Swift/MXResponse.swift | 1 - .../MXCrossSigningInfoSource.swift | 1 - .../CrossSigning/MXCrossSigningV2.swift | 1 - .../CryptoMachine/MXCryptoMachine.swift | 11 -- .../CryptoMachine/MXCryptoProtocols.swift | 9 -- .../CryptoMachine/MXCryptoRequests.swift | 4 - .../Crypto/Devices/MXDeviceInfoSource.swift | 1 - MatrixSDK/Crypto/MXCryptoV2.swift | 4 - .../Crypto/Trust/MXTrustLevelSource.swift | 1 - .../MXKeyVerificationManagerV2.swift | 2 - .../Requests/MXKeyVerificationRequestV2.swift | 1 - .../Transactions/SAS/MXSASTransactionV2.swift | 1 - .../Data/Store/MXFileStore/MXFileStore.m | 113 +++++++----------- MatrixSDK/Utils/MXMemory.swift | 4 - MatrixSDK/Utils/MXTaskQueue.swift | 1 - MatrixSDK/VoIP/MXiOSAudioOutputRouter.swift | 1 - .../VoIP/MXiOSAudioOutputRouterDelegate.swift | 1 - .../MXCrossSigningInfoSourceUnitTests.swift | 1 - .../MXCrossSigningV2UnitTests.swift | 1 - .../CryptoMachine/MXCryptoProtocolStubs.swift | 6 - .../MXCryptoRequestsUnitTests.swift | 1 - .../MXOlmInboundGroupSessionUnitTests.swift | 1 - .../Devices/MXDeviceInfoSourceUnitTests.swift | 1 - .../Trust/MXTrustLevelSourceUnitTests.swift | 1 - .../MXKeyVerificationRequestV2UnitTests.swift | 1 - .../SAS/MXSASTransactionV2UnitTests.swift | 1 - .../MXEventAnnotationUnitTests.swift | 1 - .../MXEventReferenceUnitTests.swift | 1 - .../Utils/MXTaskQueueUnitTests.swift | 1 - changelog.d/pr-1574.change | 1 + 33 files changed, 58 insertions(+), 142 deletions(-) create mode 100644 changelog.d/pr-1574.change diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index cffb2f7207..3ce3521bdb 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -22,13 +22,13 @@ Pod::Spec.new do |s| s.requires_arc = true s.swift_versions = ['5.1', '5.2'] - s.ios.deployment_target = "11.0" - s.osx.deployment_target = "10.12" + s.ios.deployment_target = "13.0" + s.osx.deployment_target = "10.15" s.default_subspec = 'Core' s.subspec 'Core' do |ss| - ss.ios.deployment_target = "11.0" - ss.osx.deployment_target = "10.12" + ss.ios.deployment_target = "13.0" + ss.osx.deployment_target = "10.15" ss.source_files = "MatrixSDK", "MatrixSDK/**/*.{h,m}", "MatrixSDK/**/*.{swift}" ss.osx.exclude_files = "MatrixSDK/VoIP/MXiOSAudioOutputRoute*.swift" @@ -49,7 +49,7 @@ Pod::Spec.new do |s| end s.subspec 'JingleCallStack' do |ss| - ss.ios.deployment_target = "12.0" + ss.ios.deployment_target = "13.0" ss.source_files = "MatrixSDKExtensions/VoIP/Jingle/**/*.{h,m}" diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 188a5c8cdc..d7de3a0b9f 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -7652,8 +7652,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_SWIFT_FLAGS = "-D DEBUG"; @@ -7709,8 +7709,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MACOSX_DEPLOYMENT_TARGET = 10.10; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = ""; SWIFT_COMPILATION_MODE = wholemodule; @@ -7845,7 +7845,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixSDK; PRODUCT_NAME = MatrixSDK; @@ -7873,7 +7873,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.15; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixSDK; PRODUCT_NAME = MatrixSDK; @@ -7905,7 +7905,6 @@ INFOPLIST_FILE = MatrixSDKTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "matrix.org.MatrixSDKTests-macOS"; @@ -7937,7 +7936,6 @@ INFOPLIST_FILE = MatrixSDKTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; - MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "matrix.org.MatrixSDKTests-macOS"; PRODUCT_NAME = MatrixSDKTests; diff --git a/MatrixSDK/Contrib/Swift/Data/MXRoom.swift b/MatrixSDK/Contrib/Swift/Data/MXRoom.swift index e80a33f950..34649801c5 100644 --- a/MatrixSDK/Contrib/Swift/Data/MXRoom.swift +++ b/MatrixSDK/Contrib/Swift/Data/MXRoom.swift @@ -39,7 +39,6 @@ public extension MXRoom { /** The current list of members of the room using async API. */ - @available(iOS 13.0.0, macOS 10.15.0, *) func members() async throws -> MXRoomMembers? { try await performCallbackRequest { members(completion: $0) diff --git a/MatrixSDK/Contrib/Swift/MXResponse.swift b/MatrixSDK/Contrib/Swift/MXResponse.swift index b120a69f1b..07df6368f8 100644 --- a/MatrixSDK/Contrib/Swift/MXResponse.swift +++ b/MatrixSDK/Contrib/Swift/MXResponse.swift @@ -227,7 +227,6 @@ internal func uncurryResponse(_ response: MXResponse, success: @escaping ( /// room.members($0) /// } /// ``` -@available(iOS 13.0.0, macOS 10.15.0, *) internal func performCallbackRequest(_ request: (@escaping (MXResponse) -> Void) -> Void) async throws -> T { return try await withCheckedThrowingContinuation { continuation in request { diff --git a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningInfoSource.swift b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningInfoSource.swift index e50e94e64f..1c6d24846d 100644 --- a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningInfoSource.swift +++ b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningInfoSource.swift @@ -20,7 +20,6 @@ import Foundation /// Convenience struct which transforms `MatrixSDKCrypto` cross signing info formats /// into `MatrixSDK` `MXCrossSigningInfo` formats. -@available(iOS 13.0.0, *) struct MXCrossSigningInfoSource { private let source: MXCryptoUserIdentitySource diff --git a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift index d4ab28c349..be6f1b5bfd 100644 --- a/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift +++ b/MatrixSDK/Crypto/CrossSigning/MXCrossSigningV2.swift @@ -21,7 +21,6 @@ import Foundation /// A work-in-progress subclass of `MXCrossSigning` instantiated and used by `MXCryptoV2`. /// /// Note: `MXCrossSigning` will be defined as a protocol in the future to avoid subclasses. -@available(iOS 13.0.0, *) class MXCrossSigningV2: MXCrossSigning { enum Error: Swift.Error { case missingAuthSession diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index 1f1d73021a..77b234c199 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -27,7 +27,6 @@ typealias GetRoomAction = (String) -> MXRoom? /// Two main responsibilities of the `MXCryptoMachine` are: /// - mapping to and from raw strings passed into the Rust machine /// - performing network requests and marking them as completed on behalf of the Rust machine -@available(iOS 13.0.0, *) class MXCryptoMachine { actor RoomQueues { private var queues = [String: MXTaskQueue]() @@ -98,7 +97,6 @@ class MXCryptoMachine { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoIdentity { var userId: String { return machine.userId() @@ -125,7 +123,6 @@ extension MXCryptoMachine: MXCryptoIdentity { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoSyncing { func handleSyncResponse( toDevice: MXToDeviceSyncResponse?, @@ -244,7 +241,6 @@ extension MXCryptoMachine: MXCryptoSyncing { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoDevicesSource { func devices(userId: String) -> [Device] { do { @@ -265,7 +261,6 @@ extension MXCryptoMachine: MXCryptoDevicesSource { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoUserIdentitySource { func isUserVerified(userId: String) -> Bool { do { @@ -292,7 +287,6 @@ extension MXCryptoMachine: MXCryptoUserIdentitySource { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoEventEncrypting { func shareRoomKeysIfNecessary(roomId: String, users: [String]) async throws { try await sessionsQueue.sync { [weak self] in @@ -376,7 +370,6 @@ extension MXCryptoMachine: MXCryptoEventEncrypting { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoCrossSigning { func crossSigningStatus() -> CrossSigningStatus { return machine.crossSigningStatus() @@ -391,7 +384,6 @@ extension MXCryptoMachine: MXCryptoCrossSigning { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoVerificationRequesting { func requestSelfVerification(methods: [String]) async throws -> VerificationRequest { guard let result = try machine.requestSelfVerification(methods: methods) else { @@ -467,7 +459,6 @@ extension MXCryptoMachine: MXCryptoVerificationRequesting { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoVerifying { func verification(userId: String, flowId: String) -> Verification? { return machine.getVerification(userId: userId, flowId: flowId) @@ -495,7 +486,6 @@ extension MXCryptoMachine: MXCryptoVerifying { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: MXCryptoSASVerifying { func startSasVerification(userId: String, flowId: String) async throws -> Sas { guard let result = try machine.startSasVerification(userId: userId, flowId: flowId) else { @@ -520,7 +510,6 @@ extension MXCryptoMachine: MXCryptoSASVerifying { } } -@available(iOS 13.0.0, *) extension MXCryptoMachine: Logger { func log(logLine: String) { MXLog.debug("[MXCryptoMachine] \(logLine)") diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift index b9e92c1c40..b7807996d2 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift @@ -23,7 +23,6 @@ import MatrixSDKCrypto /// A set of protocols defining the functionality in `MatrixSDKCrypto` and separating them into logical units /// Cryptographic identity of the currently signed-in user -@available(iOS 13.0.0, *) protocol MXCryptoIdentity { var userId: String { get } var deviceId: String { get } @@ -32,7 +31,6 @@ protocol MXCryptoIdentity { } /// Handler for cryptographic events in the sync loop -@available(iOS 13.0.0, *) protocol MXCryptoSyncing: MXCryptoIdentity { func handleSyncResponse( toDevice: MXToDeviceSyncResponse?, @@ -45,14 +43,12 @@ protocol MXCryptoSyncing: MXCryptoIdentity { } /// Source of user devices and their cryptographic trust status -@available(iOS 13.0.0, *) protocol MXCryptoDevicesSource: MXCryptoIdentity { func device(userId: String, deviceId: String) -> Device? func devices(userId: String) -> [Device] } /// Source of user identities and their cryptographic trust status -@available(iOS 13.0.0, *) protocol MXCryptoUserIdentitySource: MXCryptoIdentity { func userIdentity(userId: String) -> UserIdentity? func isUserVerified(userId: String) -> Bool @@ -60,7 +56,6 @@ protocol MXCryptoUserIdentitySource: MXCryptoIdentity { } /// Event encryption and decryption -@available(iOS 13.0.0, *) protocol MXCryptoEventEncrypting: MXCryptoIdentity { func shareRoomKeysIfNecessary(roomId: String, users: [String]) async throws func encrypt(_ content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] @@ -68,14 +63,12 @@ protocol MXCryptoEventEncrypting: MXCryptoIdentity { } /// Cross-signing functionality -@available(iOS 13.0.0, *) protocol MXCryptoCrossSigning: MXCryptoUserIdentitySource { func crossSigningStatus() -> CrossSigningStatus func bootstrapCrossSigning(authParams: [AnyHashable: Any]) async throws } /// Lifecycle of verification request -@available(iOS 13.0.0, *) protocol MXCryptoVerificationRequesting: MXCryptoIdentity { func requestSelfVerification(methods: [String]) async throws -> VerificationRequest func requestVerification(userId: String, roomId: String, methods: [String]) async throws -> VerificationRequest @@ -85,7 +78,6 @@ protocol MXCryptoVerificationRequesting: MXCryptoIdentity { } /// Lifecycle of verification transaction -@available(iOS 13.0.0, *) protocol MXCryptoVerifying: MXCryptoIdentity { func verification(userId: String, flowId: String) -> Verification? func confirmVerification(userId: String, flowId: String) async throws @@ -93,7 +85,6 @@ protocol MXCryptoVerifying: MXCryptoIdentity { } /// Lifecycle of SAS-specific verification transaction -@available(iOS 13.0.0, *) protocol MXCryptoSASVerifying: MXCryptoVerifying { func startSasVerification(userId: String, flowId: String) async throws -> Sas func acceptSasVerification(userId: String, flowId: String) async throws diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift index fe44647e1d..2427eb43ea 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift @@ -22,7 +22,6 @@ import MatrixSDKCrypto /// Convenience class to delegate network requests originating in Rust crypto module /// to the native REST API client -@available(iOS 13.0.0, *) struct MXCryptoRequests { private let restClient: MXRestClient init(restClient: MXRestClient) { @@ -109,7 +108,6 @@ struct MXCryptoRequests { } /// Convenience structs mapping Rust requests to data for native REST API requests -@available(iOS 13.0.0, *) extension MXCryptoRequests { enum Error: Swift.Error { case cannotCreateRequest @@ -178,7 +176,6 @@ extension MXCryptoRequests { } } -@available(iOS 13.0.0, *) extension UploadSigningKeysRequest { func jsonKeys() throws -> [AnyHashable: Any] { guard @@ -197,7 +194,6 @@ extension UploadSigningKeysRequest { } } -@available(iOS 13.0.0, *) extension SignatureUploadRequest { func jsonSignature() throws -> [AnyHashable: Any] { guard let signatures = MXTools.deserialiseJSONString(body) as? [AnyHashable: Any] else { diff --git a/MatrixSDK/Crypto/Devices/MXDeviceInfoSource.swift b/MatrixSDK/Crypto/Devices/MXDeviceInfoSource.swift index 7f94a6befd..f12b0ad2ff 100644 --- a/MatrixSDK/Crypto/Devices/MXDeviceInfoSource.swift +++ b/MatrixSDK/Crypto/Devices/MXDeviceInfoSource.swift @@ -20,7 +20,6 @@ import Foundation /// Convenience struct which transforms `MatrixSDKCrypto` device formats /// into `MatrixSDK` `MXDeviceInfo` formats. -@available(iOS 13.0.0, *) struct MXDeviceInfoSource { private let source: MXCryptoDevicesSource diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index 4ffe745136..842ef8079a 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -28,9 +28,6 @@ public extension MXCrypto { let log = MXNamedLog(name: "MXCryptoV2") #if os(iOS) - guard #available(iOS 13.0.0, *) else { - return nil - } guard MXSDKOptions.sharedInstance().enableCryptoV2 else { return nil } @@ -71,7 +68,6 @@ import MatrixSDKCrypto /// /// Another benefit of using a subclass and overriding every method with new implementation is that existing integration tests /// for crypto-related functionality can still run (and eventually pass) without any changes. -@available(iOS 13.0.0, *) private class MXCryptoV2: MXCrypto { enum Error: Swift.Error { case missingRoom diff --git a/MatrixSDK/Crypto/Trust/MXTrustLevelSource.swift b/MatrixSDK/Crypto/Trust/MXTrustLevelSource.swift index 566b909aa1..fd9e1fa851 100644 --- a/MatrixSDK/Crypto/Trust/MXTrustLevelSource.swift +++ b/MatrixSDK/Crypto/Trust/MXTrustLevelSource.swift @@ -20,7 +20,6 @@ import Foundation /// Convenience struct which transforms `MatrixSDKCrypto` trust levels /// into `MatrixSDK` `MXUserTrustLevel`, `MXDeviceTrustLevel` and `MXUsersTrustLevelSummary` formats. -@available(iOS 13.0.0, *) struct MXTrustLevelSource { private let userIdentitySource: MXCryptoUserIdentitySource private let devicesSource: MXCryptoDevicesSource diff --git a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift index d1afdd7873..3d851acd57 100644 --- a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift +++ b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift @@ -22,10 +22,8 @@ enum MXKeyVerificationUpdateResult { case removed } -@available(iOS 13.0.0, *) typealias MXCryptoVerification = MXCryptoVerificationRequesting & MXCryptoSASVerifying -@available(iOS 13.0.0, *) class MXKeyVerificationManagerV2: MXKeyVerificationManager { enum Error: Swift.Error { case notSupported diff --git a/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift b/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift index d290b095c5..b5f4f43cdf 100644 --- a/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift +++ b/MatrixSDK/Crypto/Verification/Requests/MXKeyVerificationRequestV2.swift @@ -21,7 +21,6 @@ import Foundation import MatrixSDKCrypto /// Verification request originating from `MatrixSDKCrypto` -@available(iOS 13.0.0, *) class MXKeyVerificationRequestV2: NSObject, MXKeyVerificationRequest { var state: MXKeyVerificationRequestState { // State as enum will be moved to MatrixSDKCrypto in the future diff --git a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift index a0ffafd0c3..c899923d9e 100644 --- a/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift +++ b/MatrixSDK/Crypto/Verification/Transactions/SAS/MXSASTransactionV2.swift @@ -21,7 +21,6 @@ import Foundation import MatrixSDKCrypto /// SAS transaction originating from `MatrixSDKCrypto` -@available(iOS 13.0.0, *) class MXSASTransactionV2: NSObject, MXSASTransaction { var state: MXSASTransactionState { diff --git a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m index 575027de42..693d11edb8 100644 --- a/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m +++ b/MatrixSDK/Data/Store/MXFileStore/MXFileStore.m @@ -2217,29 +2217,22 @@ - (MXFileRoomStore *)roomStoreForRoom:(NSString*)roomId */ - (void)saveObject:(id)object toFile:(NSString *)file { - if (@available(iOS 11.0, *)) + NSError *error; + NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:NO error:&error]; + if (error) { - NSError *error; - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object requiringSecureCoding:NO error:&error]; - if (error) - { - MXLogFailureDetails(@"[MXFileStore] Failed archiving root object", error); - return; - } - - BOOL success = [data writeToFile:file options:0 error:&error]; - if (success) - { - MXLogDebug(@"[MXFileStore] Saved data successfully"); - } - else - { - MXLogFailureDetails(@"[MXFileStore] Failed saving data", error); - } + MXLogFailureDetails(@"[MXFileStore] Failed archiving root object", error); + return; + } + + BOOL success = [data writeToFile:file options:0 error:&error]; + if (success) + { + MXLogDebug(@"[MXFileStore] Saved data successfully"); } else { - [NSKeyedArchiver archiveRootObject:object toFile:file]; + MXLogFailureDetails(@"[MXFileStore] Failed saving data", error); } } @@ -2252,30 +2245,23 @@ - (void)saveObject:(id)object toFile:(NSString *)file */ - (id)loadObjectOfClasses:(NSSet *)classes fromFile:(NSString *)file { - if (@available(iOS 11.0, *)) + NSError *error; + NSData *data = [NSData dataWithContentsOfFile:file]; + if (!data) { - NSError *error; - NSData *data = [NSData dataWithContentsOfFile:file]; - if (!data) - { - MXLogDebug(@"[MXFileStore] No data to load at file %@", file); - return nil; - } - - id object = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:data error:&error]; - if (object && !error) - { - return object; - } - else - { - MXLogFailureDetails(@"[MXFileStore] Failed loading object from class", error); - return nil; - } + MXLogDebug(@"[MXFileStore] No data to load at file %@", file); + return nil; + } + + id object = [NSKeyedUnarchiver unarchivedObjectOfClasses:classes fromData:data error:&error]; + if (object && !error) + { + return object; } else { - return [NSKeyedUnarchiver unarchiveObjectWithFile:file]; + MXLogFailureDetails(@"[MXFileStore] Failed loading object from class", error); + return nil; } } @@ -2289,38 +2275,31 @@ - (id)loadObjectOfClasses:(NSSet *)classes fromFile:(NSString *)file */ - (id)loadRootObjectWithoutSecureCodingFromFile:(NSString *)file { - if (@available(iOS 11.0, *)) + NSError *error; + NSData *data = [NSData dataWithContentsOfFile:file]; + if (!data) { - NSError *error; - NSData *data = [NSData dataWithContentsOfFile:file]; - if (!data) - { - MXLogDebug(@"[MXFileStore] No data to load at file %@", file); - return nil; - } - NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&error]; - if (error && !unarchiver) - { - MXLogFailureDetails(@"[MXFileStore] Cannot create unarchiver", error); - return nil; - } - unarchiver.requiresSecureCoding = NO; - - // Seems to be an implementation detaul - id object = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error]; - if (object && !error) - { - return object; - } - else - { - MXLogFailureDetails(@"[MXFileStore] Failed loading object from class", error); - return nil; - } + MXLogDebug(@"[MXFileStore] No data to load at file %@", file); + return nil; + } + NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&error]; + if (error && !unarchiver) + { + MXLogFailureDetails(@"[MXFileStore] Cannot create unarchiver", error); + return nil; + } + unarchiver.requiresSecureCoding = NO; + + // Seems to be an implementation detaul + id object = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error]; + if (object && !error) + { + return object; } else { - return [NSKeyedUnarchiver unarchiveObjectWithFile:file]; + MXLogFailureDetails(@"[MXFileStore] Failed loading object from class", error); + return nil; } } diff --git a/MatrixSDK/Utils/MXMemory.swift b/MatrixSDK/Utils/MXMemory.swift index e3159ff85a..a82c7becb4 100644 --- a/MatrixSDK/Utils/MXMemory.swift +++ b/MatrixSDK/Utils/MXMemory.swift @@ -72,10 +72,6 @@ public class MXMemory: NSObject { } public static func formattedMemoryAvailable() -> String { - guard #available(iOS 13.0, *) else { - return "Unsupported on this OS" - } - let freeBytes = self.memoryAvailable() let freeMB = Double(freeBytes) / 1024 / 1024 guard let formattedStr = numberFormatter.string(from: NSNumber(value: freeMB)) else { diff --git a/MatrixSDK/Utils/MXTaskQueue.swift b/MatrixSDK/Utils/MXTaskQueue.swift index 26fb5cbcc9..31e5b1a2a2 100644 --- a/MatrixSDK/Utils/MXTaskQueue.swift +++ b/MatrixSDK/Utils/MXTaskQueue.swift @@ -27,7 +27,6 @@ public typealias Block = () async throws -> T /// /// The solution to this problem is using serial task queues where work is scheduled, but only executed once all of the previously /// scheduled tasks have completed. This is an analogous mechanism to using serial `DispatchQueue`s. -@available(iOS 13.0.0, macOS 10.15.0, *) public actor MXTaskQueue { public enum Error: Swift.Error { case valueUnavailable diff --git a/MatrixSDK/VoIP/MXiOSAudioOutputRouter.swift b/MatrixSDK/VoIP/MXiOSAudioOutputRouter.swift index 4a7785f653..c6140a240f 100644 --- a/MatrixSDK/VoIP/MXiOSAudioOutputRouter.swift +++ b/MatrixSDK/VoIP/MXiOSAudioOutputRouter.swift @@ -18,7 +18,6 @@ import Foundation import AVFoundation /// Audio output router class -@available(iOS 10.0, *) @objcMembers public class MXiOSAudioOutputRouter: NSObject { diff --git a/MatrixSDK/VoIP/MXiOSAudioOutputRouterDelegate.swift b/MatrixSDK/VoIP/MXiOSAudioOutputRouterDelegate.swift index 093f5cf4d6..42ba60e296 100644 --- a/MatrixSDK/VoIP/MXiOSAudioOutputRouterDelegate.swift +++ b/MatrixSDK/VoIP/MXiOSAudioOutputRouterDelegate.swift @@ -17,7 +17,6 @@ import Foundation /// Audio output router delegate -@available(iOS 10.0, *) @objc public protocol MXiOSAudioOutputRouterDelegate: AnyObject { /// Delegate method to be called when output route changes, for both user actions and system changes diff --git a/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningInfoSourceUnitTests.swift b/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningInfoSourceUnitTests.swift index fe808c012a..cf140ecfd4 100644 --- a/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningInfoSourceUnitTests.swift +++ b/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningInfoSourceUnitTests.swift @@ -22,7 +22,6 @@ import XCTest import MatrixSDKCrypto -@available(iOS 13.0.0, *) class MXCrossSigningInfoSourceUnitTests: XCTestCase { var cryptoSource: UserIdentitySourceStub! var source: MXCrossSigningInfoSource! diff --git a/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningV2UnitTests.swift b/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningV2UnitTests.swift index 94868f3fec..c6dc03ea3a 100644 --- a/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningV2UnitTests.swift +++ b/MatrixSDKTests/Crypto/CrossSigning/MXCrossSigningV2UnitTests.swift @@ -22,7 +22,6 @@ import XCTest import MatrixSDKCrypto -@available(iOS 13.0.0, *) class MXCrossSigningV2UnitTests: XCTestCase { var crypto: CryptoCrossSigningStub! diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift index 5414778e37..87cbd406e2 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift @@ -46,7 +46,6 @@ class DevicesSourceStub: CryptoIdentityStub, MXCryptoDevicesSource { } } -@available(iOS 13.0.0, *) class UserIdentitySourceStub: CryptoIdentityStub, MXCryptoUserIdentitySource { var identities = [String: UserIdentity]() func userIdentity(userId: String) -> UserIdentity? { @@ -63,7 +62,6 @@ class UserIdentitySourceStub: CryptoIdentityStub, MXCryptoUserIdentitySource { } } -@available(iOS 13.0.0, *) class CryptoCrossSigningStub: CryptoIdentityStub, MXCryptoCrossSigning { var stubbedStatus = CrossSigningStatus( hasMaster: false, @@ -91,7 +89,6 @@ class CryptoCrossSigningStub: CryptoIdentityStub, MXCryptoCrossSigning { } } -@available(iOS 13.0.0, *) class CryptoVerificationStub: CryptoIdentityStub { var stubbedRequests = [String: VerificationRequest]() var stubbedTransactions = [String: Verification]() @@ -99,7 +96,6 @@ class CryptoVerificationStub: CryptoIdentityStub { var stubbedEmojis = [String: [Int]]() } -@available(iOS 13.0.0, *) extension CryptoVerificationStub: MXCryptoVerificationRequesting { func requestSelfVerification(methods: [String]) async throws -> VerificationRequest { .stub() @@ -126,7 +122,6 @@ extension CryptoVerificationStub: MXCryptoVerificationRequesting { } } -@available(iOS 13.0.0, *) extension CryptoVerificationStub: MXCryptoVerifying { func verification(userId: String, flowId: String) -> Verification? { return stubbedTransactions[flowId] @@ -136,7 +131,6 @@ extension CryptoVerificationStub: MXCryptoVerifying { } } -@available(iOS 13.0.0, *) extension CryptoVerificationStub: MXCryptoSASVerifying { func startSasVerification(userId: String, flowId: String) async throws -> Sas { .stub() diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift index e3a3c507ec..82922ae3f5 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift @@ -21,7 +21,6 @@ import Foundation import MatrixSDKCrypto -@available(iOS 13.0.0, *) class MXCryptoRequestsUnitTests: XCTestCase { func test_canCreateToDeviceRequest() { let body: [String: [String: NSDictionary]] = [ diff --git a/MatrixSDKTests/Crypto/Data/MXOlmInboundGroupSessionUnitTests.swift b/MatrixSDKTests/Crypto/Data/MXOlmInboundGroupSessionUnitTests.swift index 9ec0590682..26a06a9585 100644 --- a/MatrixSDKTests/Crypto/Data/MXOlmInboundGroupSessionUnitTests.swift +++ b/MatrixSDKTests/Crypto/Data/MXOlmInboundGroupSessionUnitTests.swift @@ -35,7 +35,6 @@ class MXOlmInboundGroupSessionUnitTests: XCTestCase { XCTAssert(data?.sharedHistory == true) } - @available(iOS 11.0, *) func testCanEncodeAndDecodeObject() { let session = MXOlmInboundGroupSession() session.senderKey = "A" diff --git a/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift b/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift index 87eb924f91..048b469e39 100644 --- a/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift +++ b/MatrixSDKTests/Crypto/Devices/MXDeviceInfoSourceUnitTests.swift @@ -22,7 +22,6 @@ import XCTest import MatrixSDKCrypto -@available(iOS 13.0.0, *) class MXDeviceInfoSourceUnitTests: XCTestCase { var cryptoSource: DevicesSourceStub! var source: MXDeviceInfoSource! diff --git a/MatrixSDKTests/Crypto/Trust/MXTrustLevelSourceUnitTests.swift b/MatrixSDKTests/Crypto/Trust/MXTrustLevelSourceUnitTests.swift index 69b6e5fc86..fcfb739304 100644 --- a/MatrixSDKTests/Crypto/Trust/MXTrustLevelSourceUnitTests.swift +++ b/MatrixSDKTests/Crypto/Trust/MXTrustLevelSourceUnitTests.swift @@ -22,7 +22,6 @@ import XCTest import MatrixSDKCrypto -@available(iOS 13.0.0, *) class MXTrustLevelSourceUnitTests: XCTestCase { var userIdentitySource: UserIdentitySourceStub! var devicesSource: DevicesSourceStub! diff --git a/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift b/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift index bb1d9ffc9b..77a801c4bb 100644 --- a/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift +++ b/MatrixSDKTests/Crypto/Verification/Requests/MXKeyVerificationRequestV2UnitTests.swift @@ -22,7 +22,6 @@ import XCTest import MatrixSDKCrypto @testable import MatrixSDK -@available(iOS 13.0.0, *) class MXKeyVerificationRequestV2UnitTests: XCTestCase { enum Error: Swift.Error, Equatable { case dummy diff --git a/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift b/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift index 8fb770f565..34ca197f0e 100644 --- a/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift +++ b/MatrixSDKTests/Crypto/Verification/Transactions/SAS/MXSASTransactionV2UnitTests.swift @@ -22,7 +22,6 @@ import XCTest import MatrixSDKCrypto @testable import MatrixSDK -@available(iOS 13.0.0, *) class MXSASTransactionV2UnitTests: XCTestCase { var verification: CryptoVerificationStub! override func setUp() { diff --git a/MatrixSDKTests/MXEventAnnotationUnitTests.swift b/MatrixSDKTests/MXEventAnnotationUnitTests.swift index fea7d607f9..61409d7c3c 100644 --- a/MatrixSDKTests/MXEventAnnotationUnitTests.swift +++ b/MatrixSDKTests/MXEventAnnotationUnitTests.swift @@ -66,7 +66,6 @@ class MXEventAnnotationUnitTests: XCTestCase { } } - @available(iOS 9.0, OSX 10.11, *) func testNSCoding() { let event = MXEvent(fromJSON: eventJSON) diff --git a/MatrixSDKTests/MXEventReferenceUnitTests.swift b/MatrixSDKTests/MXEventReferenceUnitTests.swift index 5a4ed29ab4..2b50552760 100644 --- a/MatrixSDKTests/MXEventReferenceUnitTests.swift +++ b/MatrixSDKTests/MXEventReferenceUnitTests.swift @@ -65,7 +65,6 @@ class MXEventReferenceUnitTests: XCTestCase { } } - @available(iOS 9.0, OSX 10.11, *) func testNSCoding() { let event = MXEvent(fromJSON: eventJSON) diff --git a/MatrixSDKTests/Utils/MXTaskQueueUnitTests.swift b/MatrixSDKTests/Utils/MXTaskQueueUnitTests.swift index 09b913fc78..a1e2dac217 100644 --- a/MatrixSDKTests/Utils/MXTaskQueueUnitTests.swift +++ b/MatrixSDKTests/Utils/MXTaskQueueUnitTests.swift @@ -18,7 +18,6 @@ import Foundation import XCTest @testable import MatrixSDK -@available(iOS 13.0.0, macOS 10.15.0, *) class MXTaskQueueUnitTests: XCTestCase { /// Dummy error that can be thrown by a task enum Error: Swift.Error { diff --git a/changelog.d/pr-1574.change b/changelog.d/pr-1574.change new file mode 100644 index 0000000000..afcc67330f --- /dev/null +++ b/changelog.d/pr-1574.change @@ -0,0 +1 @@ +Upgrade minimum iOS and OSX deployment target to 13.0 and 10.15 respectively From da216b6e03ad826e95e7a21314962f4715451b15 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 16 Sep 2022 10:42:27 +0100 Subject: [PATCH 08/30] Extract key backup engine --- MatrixSDK.xcodeproj/project.pbxproj | 36 +- .../KeyBackup/Engine/MXKeyBackupEngine.h | 171 +++++ .../KeyBackup/Engine/MXKeyBackupPayload.swift | 30 + .../Engine/MXNativeKeyBackupEngine.h | 37 ++ .../Engine/MXNativeKeyBackupEngine.m | 625 +++++++++++++++++ MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h | 5 - MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m | 626 +++--------------- .../Crypto/KeyBackup/MXKeyBackup_Private.h | 12 +- MatrixSDK/Crypto/MXCrypto.m | 16 +- MatrixSDK/MXRestClient.h | 2 +- MatrixSDK/MXRestClient.m | 14 +- MatrixSDKTests/MXBaseKeyBackupTests.m | 16 +- changelog.d/pr-1578.change | 1 + 13 files changed, 1024 insertions(+), 567 deletions(-) create mode 100644 MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h create mode 100644 MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift create mode 100644 MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.h create mode 100644 MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m create mode 100644 changelog.d/pr-1578.change diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index d7de3a0b9f..f2666c47f9 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1211,7 +1211,7 @@ B14EF3522397E90400758AF0 /* MXSASTransaction.h in Headers */ = {isa = PBXBuildFile; fileRef = 321CFDE422525A49004D31DF /* MXSASTransaction.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF3532397E90400758AF0 /* MXNotificationCenter.h in Headers */ = {isa = PBXBuildFile; fileRef = 32DC15CD1A8CF7AE006F9AD3 /* MXNotificationCenter.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF3542397E90400758AF0 /* MXLoginPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 3275FD9A21A6B60B00B9C13D /* MXLoginPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B14EF3552397E90400758AF0 /* MXCryptoTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 3250E7C8220C913900736CB5 /* MXCryptoTools.h */; }; + B14EF3552397E90400758AF0 /* MXCryptoTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 3250E7C8220C913900736CB5 /* MXCryptoTools.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF3562397E90400758AF0 /* MXGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = F0173EAA1FCF0E8800B5F6A3 /* MXGroup.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF3572397E90400758AF0 /* MX3PidAddSession.h in Headers */ = {isa = PBXBuildFile; fileRef = 32D2CC0123422462002BD8CA /* MX3PidAddSession.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF3582397E90400758AF0 /* MXUser.h in Headers */ = {isa = PBXBuildFile; fileRef = 329FB17D1A0B665800A5E88E /* MXUser.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1895,6 +1895,10 @@ EDBCF33A281A8D3D00ED5044 /* MXSharedHistoryKeyService.m in Sources */ = {isa = PBXBuildFile; fileRef = EDBCF338281A8D3D00ED5044 /* MXSharedHistoryKeyService.m */; }; EDC2A0E628369E740039F3D6 /* CryptoTests.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = EDC2A0E528369E740039F3D6 /* CryptoTests.xctestplan */; }; EDC2A0E728369E740039F3D6 /* CryptoTests.xctestplan in Resources */ = {isa = PBXBuildFile; fileRef = EDC2A0E528369E740039F3D6 /* CryptoTests.xctestplan */; }; + EDD4197E28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */; }; + EDD4197F28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */; }; + EDD4198128DCAA7B007F3757 /* MXNativeKeyBackupEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */; }; + EDD4198228DCAA7B007F3757 /* MXNativeKeyBackupEngine.m in Sources */ = {isa = PBXBuildFile; fileRef = EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */; }; EDD578E12881C37C006739DD /* MXDeviceInfoSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD578DC2881C37C006739DD /* MXDeviceInfoSource.swift */; }; EDD578E22881C37C006739DD /* MXDeviceInfoSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD578DC2881C37C006739DD /* MXDeviceInfoSource.swift */; }; EDD578E32881C37C006739DD /* MXTrustLevelSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD578DD2881C37C006739DD /* MXTrustLevelSource.swift */; }; @@ -1909,6 +1913,11 @@ EDD578ED2881C38C006739DD /* MXCrossSigningV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDD578EB2881C38C006739DD /* MXCrossSigningV2.swift */; }; EDE1B13B28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */; }; EDE1B13C28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */; }; + EDE70DC528DA1B7F00099736 /* MXCryptoTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 3250E7C8220C913900736CB5 /* MXCryptoTools.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDE70DC828DA22F800099736 /* MXKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDE70DC928DA22F800099736 /* MXKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDEC2E5E28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */; }; + EDEC2E5F28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */; }; EDF1B6902876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */; }; EDF1B6912876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */; }; EDF1B6932876CD8600BBBCEE /* MXTaskQueueUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B6922876CD8600BBBCEE /* MXTaskQueueUnitTests.swift */; }; @@ -2959,6 +2968,8 @@ 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 = ""; }; EDC2A0E528369E740039F3D6 /* CryptoTests.xctestplan */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CryptoTests.xctestplan; sourceTree = ""; }; + EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXNativeKeyBackupEngine.h; sourceTree = ""; }; + EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXNativeKeyBackupEngine.m; sourceTree = ""; }; EDD578DC2881C37C006739DD /* MXDeviceInfoSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXDeviceInfoSource.swift; sourceTree = ""; }; EDD578DD2881C37C006739DD /* MXTrustLevelSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXTrustLevelSource.swift; sourceTree = ""; }; EDD578DE2881C37C006739DD /* MXCrossSigningInfoSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCrossSigningInfoSource.swift; sourceTree = ""; }; @@ -2966,6 +2977,8 @@ EDD578E02881C37C006739DD /* MXCryptoUserIdentityWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoUserIdentityWrapper.swift; sourceTree = ""; }; EDD578EB2881C38C006739DD /* MXCrossSigningV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCrossSigningV2.swift; sourceTree = ""; }; EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCrossSigningV2UnitTests.swift; sourceTree = ""; }; + EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXKeyBackupEngine.h; sourceTree = ""; }; + EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXKeyBackupPayload.swift; sourceTree = ""; }; EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXTaskQueue.swift; sourceTree = ""; }; EDF1B6922876CD8600BBBCEE /* MXTaskQueueUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXTaskQueueUnitTests.swift; sourceTree = ""; }; EDF4678627E3331D00435913 /* EventsEnumeratorDataSourceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsEnumeratorDataSourceStub.swift; sourceTree = ""; }; @@ -3975,6 +3988,7 @@ 32BBAE642178E99100D85F46 /* KeyBackup */ = { isa = PBXGroup; children = ( + EDE70DC628DA22E200099736 /* Engine */, 32BBAE652178E99100D85F46 /* Data */, 32BBAE722179CF4000D85F46 /* MXKeyBackup.h */, 32BBAE732179CF4000D85F46 /* MXKeyBackup.m */, @@ -5350,6 +5364,17 @@ path = JSONModels; sourceTree = ""; }; + EDE70DC628DA22E200099736 /* Engine */ = { + isa = PBXGroup; + children = ( + EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */, + EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */, + EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */, + EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */, + ); + path = Engine; + sourceTree = ""; + }; F03EF4F91DF014D9009DF592 /* Media */ = { isa = PBXGroup; children = ( @@ -5464,6 +5489,7 @@ 327F8DB21C6112BA00581CA3 /* MXRoomThirdPartyInvite.h in Headers */, B19A309E240424BD00FB6F35 /* MXQRCodeTransaction_Private.h in Headers */, EC40384A289A7F260067D5B8 /* MXAes256KeyBackupAlgorithm.h in Headers */, + EDD4197E28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h in Headers */, 32BBAE742179CF4000D85F46 /* MXKeyBackup.h in Headers */, 92634B821EF2E3C400DB9F60 /* MXCallKitConfiguration.h in Headers */, 32618E7120ED2DF500E1D2EA /* MXFilterJSONModel.h in Headers */, @@ -5552,6 +5578,7 @@ EC619D9324DD834B00663A80 /* MXPushGatewayRestClient.h in Headers */, 32C78B68256CFC4D008130B1 /* MXCryptoVersion.h in Headers */, 320B3934239FA56900BE2C06 /* MXKeyVerificationByDMRequest.h in Headers */, + EDE70DC528DA1B7F00099736 /* MXCryptoTools.h in Headers */, EC60EDE8265CFF3100B39A4E /* MXRoomInviteState.h in Headers */, 324DD2A6246AE81300377005 /* MXSecretStorageKeyContent.h in Headers */, EC60ED8F265CFD3B00B39A4E /* MXRoomSync.h in Headers */, @@ -5614,6 +5641,7 @@ 321CFDFF2254E8C4004D31DF /* MXEmojiRepresentation.h in Headers */, 324BE4681E3FADB1008D99D4 /* MXMegolmExportEncryption.h in Headers */, EC8A539B25B1BC77004E0802 /* MXCallAnswerEventContent.h in Headers */, + EDE70DC828DA22F800099736 /* MXKeyBackupEngine.h in Headers */, ECDA764C27BA963D000C48CF /* MXBooleanCapability.h in Headers */, 3274538A23FD918800438328 /* MXKeyVerificationByToDeviceRequest.h in Headers */, B1136963230AC9D900E2B2FA /* MXIdentityServerRestClient.h in Headers */, @@ -5844,6 +5872,7 @@ B14EF2BE2397E90400758AF0 /* MXEvent.h in Headers */, EC0B9430271D95CC00B4D440 /* MXFileRoomSummaryStore.h in Headers */, B14EF2BF2397E90400758AF0 /* MXRoomThirdPartyInvite.h in Headers */, + EDD4197F28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h in Headers */, ECBF657F26DE2A4900AA3A99 /* MXMemoryRoomOutgoingMessagesStore.h in Headers */, EC8A538C25B1BC77004E0802 /* MXCallCandidate.h in Headers */, B14EF2C12397E90400758AF0 /* MXKeyBackup.h in Headers */, @@ -6034,6 +6063,7 @@ B14EF32E2397E90400758AF0 /* MXMediaLoader.h in Headers */, ECDA763A27B6B74C000C48CF /* MXCapabilities.h in Headers */, B14EF32F2397E90400758AF0 /* MXAccountData.h in Headers */, + EDE70DC928DA22F800099736 /* MXKeyBackupEngine.h in Headers */, B135066C27EA1F5E00BD3276 /* MXEventAssetType.h in Headers */, B14EF3302397E90400758AF0 /* MXEnumConstants.h in Headers */, ECBF658626DE3DF800AA3A99 /* MXFileRoomOutgoingMessagesStore.h in Headers */, @@ -6477,12 +6507,14 @@ 32C235731F827F3800E38FC5 /* MXRoomOperation.m in Sources */, 322A51C41D9AC8FE00C8536D /* MXCryptoAlgorithms.m in Sources */, B1798D0824091A0100308A8F /* MXBase64Tools.m in Sources */, + EDEC2E5E28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */, B182B08823167A640057972E /* MXIdentityServerHashDetails.m in Sources */, EC60EDBE265CFE8600B39A4E /* MXRoomSyncAccountData.m in Sources */, EC1165BE27107E330089FA56 /* MXSuggestedRoomListDataFetcher.swift in Sources */, 324DD2A2246AE1EF00377005 /* MXEncryptedSecretContent.m in Sources */, 329FB1761A0A3A1600A5E88E /* MXRoomMember.m in Sources */, B16C2455283AB0DE00F5D1FE /* MXRealmBeaconMapper.swift in Sources */, + EDD4198128DCAA7B007F3757 /* MXNativeKeyBackupEngine.m in Sources */, 3251D41F25AF01D7001E6E77 /* MXUIKitApplicationStateService.swift in Sources */, B1136965230AC9D900E2B2FA /* MXIdentityService.m in Sources */, 66836AB727CFA17200515780 /* MXEventStreamService.swift in Sources */, @@ -7076,12 +7108,14 @@ B14EF1F32397E90400758AF0 /* MXServerNotices.m in Sources */, B14EF1F42397E90400758AF0 /* MXMemoryStore.m in Sources */, B14EF1F52397E90400758AF0 /* MXAggregationPaginatedResponse.m in Sources */, + EDEC2E5F28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */, B14EF1F62397E90400758AF0 /* MXEventReplace.m in Sources */, B16F35A325F916A00029AE98 /* MXRoomTypeMapper.swift in Sources */, EC1165BF27107E330089FA56 /* MXSuggestedRoomListDataFetcher.swift in Sources */, B14EF1F72397E90400758AF0 /* MXLoginTerms.m in Sources */, B14EF1F82397E90400758AF0 /* MXReplyEventParts.m in Sources */, B16C2456283AB0DE00F5D1FE /* MXRealmBeaconMapper.swift in Sources */, + EDD4198228DCAA7B007F3757 /* MXNativeKeyBackupEngine.m in Sources */, 3259D02426037A7200C365DB /* NSArray.swift in Sources */, 3A108A8125810C96005EEBE9 /* MXKeyData.m in Sources */, 66836AB827CFA17200515780 /* MXEventStreamService.swift in Sources */, diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h b/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h new file mode 100644 index 0000000000..227fa8a69f --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h @@ -0,0 +1,171 @@ +// +// 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 MXKeyBackupStore_h +#define MXKeyBackupStore_h + +#import "MXMegolmSessionData.h" +#import "MXMegolmBackupCreationInfo.h" +#import "MXKeyBackupVersionTrust.h" +#import "MXKeyBackupAlgorithm.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MXKeyBackupPayload; + +/** + Backup engine responsible for managing and storing internal key backups, incl private keys and room keys + */ +@protocol MXKeyBackupEngine + +#pragma mark - Enable / Disable engine + +/** + Is the engine enabled to backup room keys + */ +@property (nonatomic, readonly) BOOL enabled; + +/** + Current version of the backup + */ +@property (nonatomic, readonly) NSString *version; + +/** + Enable a new backup version that will replace any previous version + */ +- (BOOL)enableBackupWithVersion:(MXKeyBackupVersion *)version + error:(NSError **)error; +/** + Disable the current backup and reset any backup-related state + */ +- (void)disableBackup; + +#pragma mark - Private / Recovery key management + +/** + Get the private key of the current backup version + */ +- (nullable NSData *)privateKey; + +/** + Save a new private key + */ +- (void)savePrivateKey:(NSString *)privateKey; + +/** + Save a new private key using a recovery key + */ +- (void)saveRecoveryKey:(NSString *)recoveryKey; + +/** + Delete the currently stored private key + */ +- (void)deletePrivateKey; + +/** + Check if a private key matches current key backup version + */ +- (BOOL)isValidPrivateKey:(NSData *)privateKey + error:(NSError **)error; + +/** + Check if a private key matches key backup version + */ +- (BOOL)isValidPrivateKey:(NSData *)privateKey + forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error; + +/** + Check if a recovery key matches key backup authentication data + */ +- (BOOL)isValidRecoveryKey:(NSString *)recoveryKey + forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error; + +- (void)validateKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion; + +/** + Compute the recovery key from a password and key backup auth data + */ +- (nullable NSString *)recoveryKeyFromPassword:(NSString *)password + inKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error; + +#pragma mark - Backup versions + +/** + Prepare a new backup version to be uploaded to the server + */ +- (void)prepareKeyBackupVersionWithPassword:(NSString *)password + algorithm:(NSString *)algorithm + success:(void (^)(MXMegolmBackupCreationInfo *))success + failure:(void (^)(NSError *))failure; + +/** + Get the current trust level of the backup version + */ +- (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBackupVersion *)keyBackupVersion; + +/** + Extract authentication data from a backup + */ +- (nullable id)megolmBackupAuthDataFromKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error; + +/** + Sign an object with backup signing key + */ +- (NSDictionary *)signObject:(NSDictionary *)object; + +#pragma mark - Backup keys + +/** + Are there any keys that have not yet been backed up + */ +- (BOOL)hasKeysToBackup; + +/** + The ratio of total vs backed up keys + */ +- (NSProgress *)backupProgress; + +/** + Payload of room keys to be backed up to the server + */ +- (nullable MXKeyBackupPayload *)roomKeysBackupPayload; + +/** + Decrypt backup data using private key + */ +- (nullable MXMegolmSessionData *)decryptKeyBackupData:(MXKeyBackupData *)keyBackupData + keyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + privateKey:(NSData *)privateKey + forSession:(NSString *)sessionId + inRoom:(NSString *)roomId; + +/** + Import decrypted room keys + */ +- (void)importMegolmSessionDatas:(NSArray*)keys + backUp:(BOOL)backUp + success:(void (^)(NSUInteger total, NSUInteger imported))success + failure:(void (^)(NSError *error))failure; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* MXKeyBackupStore_h */ diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift b/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift new file mode 100644 index 0000000000..12f228efd6 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift @@ -0,0 +1,30 @@ +// +// 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 + +/// Payload of room keys to backup and a completion block to call after +/// successful backup. +@objcMembers +public class MXKeyBackupPayload: NSObject { + public let backupData: MXKeysBackupData + public let completion: ([AnyHashable: Any]) -> Void + + @objc public init(backupData: MXKeysBackupData, completion: @escaping ([AnyHashable: Any]) -> Void) { + self.backupData = backupData + self.completion = completion + } +} diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.h b/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.h new file mode 100644 index 0000000000..2896c6df5c --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.h @@ -0,0 +1,37 @@ +// +// 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 MXNativeKeyBackupEngine_h +#define MXNativeKeyBackupEngine_h + +#import "MXKeyBackupEngine.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MXNativeKeyBackupEngine : NSObject + +- (instancetype)initWithCrypto:(MXCrypto *)crypto; + +/** + The backup algorithm being used. Nil if key backup not enabled yet. + */ +@property (nonatomic, nullable, readonly) id keyBackupAlgorithm; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* MXNativeKeyBackupEngine_h */ diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m b/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m new file mode 100644 index 0000000000..19983bd7f9 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m @@ -0,0 +1,625 @@ +// +// 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 +#import "MXNativeKeyBackupEngine.h" +#import "MXCrypto.h" +#import "MXCrypto_Private.h" +#import "MXCrossSigning_Private.h" +#import "MXKeyBackupAlgorithm.h" +#import "OLMInboundGroupSession.h" +#import "MatrixSDKSwiftHeader.h" +#import "MXRecoveryKey.h" + +/** + Maximum number of keys to send at a time to the homeserver. + */ +NSUInteger const kMXKeyBackupSendKeysMaxCount = 100; + +static NSDictionary> *AlgorithmClassesByName; +static Class DefaultAlgorithmClass; + +@interface MXNativeKeyBackupEngine () + +@property (nonatomic, weak) MXCrypto *crypto; +@property (nonatomic, nullable) MXKeyBackupVersion *keyBackupVersion; +@property (nonatomic, nullable) id keyBackupAlgorithm; + +@end + +@implementation MXNativeKeyBackupEngine + ++ (void)initialize +{ + if (MXSDKOptions.sharedInstance.enableSymmetricBackup) + { + AlgorithmClassesByName = @{ + kMXCryptoCurve25519KeyBackupAlgorithm: MXCurve25519KeyBackupAlgorithm.class, + kMXCryptoAes256KeyBackupAlgorithm: MXAes256KeyBackupAlgorithm.class + }; + } + else + { + AlgorithmClassesByName = @{ + kMXCryptoCurve25519KeyBackupAlgorithm: MXCurve25519KeyBackupAlgorithm.class, + }; + } + DefaultAlgorithmClass = MXCurve25519KeyBackupAlgorithm.class; +} + +- (instancetype)initWithCrypto:(MXCrypto *)crypto +{ + self = [self init]; + if (self) + { + _crypto = crypto; + } + return self; +} + +#pragma mark - Enable / Disable engine + +- (BOOL)enabled +{ + return self.version != nil; +} + +- (NSString *)version +{ + return self.crypto.store.backupVersion; +} + +- (BOOL)enableBackupWithVersion:(MXKeyBackupVersion *)version error:(NSError **)error +{ + id authData = [self megolmBackupAuthDataFromKeyBackupVersion:version error:error]; + if (!authData) + { + return NO; + } + + self.keyBackupVersion = version; + self.crypto.store.backupVersion = version.version; + Class algorithmClass = AlgorithmClassesByName[version.algorithm]; + // store the desired backup algorithm + self.keyBackupAlgorithm = [[algorithmClass alloc] initWithCrypto:self.crypto authData:authData keyGetterBlock:^NSData * _Nullable{ + return self.privateKey; + }]; + MXLogDebug(@"[MXNativeKeyBackupEngine] enableBackupWithVersion: Algorithm set to: %@", self.keyBackupAlgorithm); + return YES; +} + +- (void)disableBackup +{ + self.keyBackupVersion = nil; + self.crypto.store.backupVersion = nil; + [self.crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; + self.keyBackupAlgorithm = nil; + + // Reset backup markers + [self.crypto.store resetBackupMarkers]; +} + +#pragma mark - Private / Recovery key management + +- (nullable NSData*)privateKey +{ + NSString *privateKeyBase64 = [self.crypto.store secretWithSecretId:MXSecretId.keyBackup]; + if (!privateKeyBase64) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] privateKey. Error: No secret in crypto store"); + return nil; + } + + return [MXBase64Tools dataFromBase64:privateKeyBase64]; +} + +- (void)savePrivateKey:(NSString *)privateKey +{ + [self.crypto.store storeSecret:privateKey withSecretId:MXSecretId.keyBackup]; +} + +- (void)saveRecoveryKey:(NSString *)recoveryKey +{ + NSError *error; + OLMPkDecryption *decryption = [self pkDecryptionFromRecoveryKey:recoveryKey error:&error]; + if (!decryption) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] saveRecoveryKey: Cannot create OLMPkDecryption. Error: %@", error); + return; + } + + NSString *privateKeyBase64 = [MXBase64Tools unpaddedBase64FromData:decryption.privateKey]; + [self savePrivateKey:privateKeyBase64]; +} + +- (void)deletePrivateKey +{ + [self.crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; +} + +- (BOOL)isValidPrivateKey:(NSData *)privateKey + error:(NSError **)error +{ + return [self.keyBackupAlgorithm keyMatches:privateKey error:error]; +} + +- (BOOL)isValidPrivateKey:(NSData *)privateKey + forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error +{ + id algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey]; + return [algorithm keyMatches:privateKey error:error]; +} + +- (BOOL)isValidRecoveryKey:(NSString*)recoveryKey + forKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion + error:(NSError **)error +{ + NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:error]; + + if (*error) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] isValidRecoveryKey: Invalid recovery key. Error: %@", *error); + + // Return a generic error + *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorInvalidRecoveryKeyCode + userInfo:@{ + NSLocalizedDescriptionKey: @"Invalid recovery key or password" + }]; + return NO; + } + + Class algorithm = AlgorithmClassesByName[keyBackupVersion.algorithm]; + if (algorithm == NULL) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] isValidRecoveryKey: unknown algorithm: %@", keyBackupVersion.algorithm); + + *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorUnknownAlgorithm + userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Unknown algorithm (%@)", keyBackupVersion.algorithm] + }]; + return NO; + } + BOOL result = [algorithm keyMatches:privateKey withAuthData:keyBackupVersion.authData error:error]; + + if (!result) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] isValidRecoveryKey: Public keys mismatch"); + + *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorInvalidRecoveryKeyCode + userInfo:@{ + NSLocalizedDescriptionKey: @"Invalid recovery key or password: public keys mismatch" + }]; + } + + return result; +} + +- (void)validateKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion +{ + // Check private keys + if (self.privateKey) + { + Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; + if (algorithmClass == NULL) + { + NSString *message = [NSString stringWithFormat:@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: unknown algorithm: %@", keyBackupVersion.algorithm]; + MXLogError(message); + return; + } + if (![algorithmClass checkBackupVersion:keyBackupVersion]) + { + MXLogError(@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: invalid backup data returned"); + return; + } + + NSData *privateKey = self.privateKey; + NSError *error; + BOOL keyMatches = [algorithmClass keyMatches:privateKey withAuthData:keyBackupVersion.authData error:&error]; + if (error || !keyMatches) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: -> private key does not match: %@, will be removed", error); + [self.crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; + } + } +} + +- (nullable NSString*)recoveryKeyFromPassword:(NSString*)password + inKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion + error:(NSError **)error +{ + // Extract MXBaseKeyBackupAuthData + id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:error]; + if (*error) + { + return nil; + } + + if (!authData.privateKeySalt || !authData.privateKeyIterations) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] recoveryFromPassword: Salt and/or iterations not found in key backup auth data"); + *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorMissingPrivateKeySaltCode + userInfo:@{ + NSLocalizedDescriptionKey: @"Salt and/or iterations not found in key backup auth data" + }]; + return nil; + } + + + // Extract the recovery key from the passphrase + NSData *recoveryKeyData = [MXKeyBackupPassword retrievePrivateKeyWithPassword:password salt:authData.privateKeySalt iterations:authData.privateKeyIterations error:error]; + if (*error) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] recoveryFromPassword: retrievePrivateKeyWithPassword failed: %@", *error); + return nil; + } + + return [MXRecoveryKey encode:recoveryKeyData]; +} + +#pragma mark - Backup versions + +- (void)prepareKeyBackupVersionWithPassword:(NSString *)password + algorithm:(NSString *)algorithm + success:(void (^)(MXMegolmBackupCreationInfo *))success + failure:(void (^)(NSError *))failure +{ + Class algorithmClass = algorithm ? AlgorithmClassesByName[algorithm] : DefaultAlgorithmClass; + if (algorithmClass == NULL) + { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + NSError *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorUnknownAlgorithm + userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Unknown algorithm (%@) to prepare the backup", algorithm] + }]; + failure(error); + }); + } + return; + } + NSError *error; + MXKeyBackupPreparationInfo *preparationInfo = [algorithmClass prepareWith:password error:&error]; + if (error) + { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + return; + } + id authData = preparationInfo.authData; + + MXMegolmBackupCreationInfo *keyBackupCreationInfo = [MXMegolmBackupCreationInfo new]; + keyBackupCreationInfo.algorithm = [algorithmClass algorithmName]; + keyBackupCreationInfo.authData = authData; + keyBackupCreationInfo.recoveryKey = [MXRecoveryKey encode:preparationInfo.privateKey]; + + NSString *myUserId = self.crypto.matrixRestClient.credentials.userId; + NSMutableDictionary *signatures = [NSMutableDictionary dictionary]; + + NSDictionary *deviceSignature = [self.crypto signObject:authData.signalableJSONDictionary]; + [signatures addEntriesFromDictionary:deviceSignature[myUserId]]; + + if ([self.crypto.crossSigning canCrossSign] == NO) + { + authData.signatures = @{myUserId: signatures}; + keyBackupCreationInfo.authData = authData; + + if (success) + { + dispatch_async(dispatch_get_main_queue(), ^{ + success(keyBackupCreationInfo); + }); + } + + return; + } + + [self.crypto.crossSigning signObject:authData.signalableJSONDictionary withKeyType:MXCrossSigningKeyType.master success:^(NSDictionary *signedObject) { + + [signatures addEntriesFromDictionary:signedObject[@"signatures"][myUserId]]; + + authData.signatures = @{myUserId: signatures}; + keyBackupCreationInfo.authData = authData; + + if (success) + { + dispatch_async(dispatch_get_main_queue(), ^{ + success(keyBackupCreationInfo); + }); + } + } failure:^(NSError *error) { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; +} + +- (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBackupVersion *)keyBackupVersion +{ + NSString *myUserId = self.crypto.matrixRestClient.credentials.userId; + + MXKeyBackupVersionTrust *keyBackupVersionTrust = [MXKeyBackupVersionTrust new]; + + NSError *error; + id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; + if (error) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] trustForKeyBackupVersion: Key backup is absent or missing required data"); + return keyBackupVersionTrust; + } + + NSData *privateKey = self.privateKey; + if (privateKey) + { + id algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey]; + if ([algorithm keyMatches:privateKey error:nil]) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] trustForKeyBackupVersionFromCryptoQueue: Backup is trusted locally"); + keyBackupVersionTrust.trustedLocally = YES; + } + } + + NSDictionary *mySigs = authData.signatures[myUserId]; + NSMutableArray *signatures = [NSMutableArray array]; + for (NSString *keyId in mySigs) + { + // XXX: is this how we're supposed to get the device id? + NSString *deviceId; + NSArray *components = [keyId componentsSeparatedByString:@":"]; + if (components.count == 2) + { + deviceId = components[1]; + } + + if (deviceId) + { + BOOL valid = NO; + + MXDeviceInfo *device = [self.crypto.deviceList storedDevice:myUserId deviceId:deviceId]; + if (device) + { + NSError *error; + valid = [self.crypto.olmDevice verifySignature:device.fingerprint JSON:authData.signalableJSONDictionary signature:mySigs[keyId] error:&error]; + + if (!valid) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] trustForKeyBackupVersion: Bad signature from device %@: %@", device.deviceId, error); + } + + MXKeyBackupVersionTrustSignature *signature = [MXKeyBackupVersionTrustSignature new]; + signature.deviceId = deviceId; + signature.device = device; + signature.valid = valid; + [signatures addObject:signature]; + } + else // Try interpreting it as the MSK public key + { + NSError *error; + BOOL valid = [self.crypto.crossSigning.crossSigningTools pkVerifyObject:authData.JSONDictionary userId:myUserId publicKey:deviceId error:&error]; + + if (!valid) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] trustForKeyBackupVersion: Signature with unknown key %@", deviceId); + } + else + { + MXKeyBackupVersionTrustSignature *signature = [MXKeyBackupVersionTrustSignature new]; + signature.keys = deviceId; + signature.valid = valid; + [signatures addObject:signature]; + } + } + } + } + + keyBackupVersionTrust.signatures = signatures; + + for (MXKeyBackupVersionTrustSignature *signature in keyBackupVersionTrust.signatures) + { + if (signature.valid && signature.device && signature.device.trustLevel.isVerified) + { + keyBackupVersionTrust.usable = YES; + } + } + keyBackupVersionTrust.usable = keyBackupVersionTrust.usable || keyBackupVersionTrust.isTrustedLocally; + + return keyBackupVersionTrust; +} + +- (nullable id)megolmBackupAuthDataFromKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion error:(NSError**)error +{ + Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; + if (algorithmClass == NULL) + { + NSString *message = [NSString stringWithFormat:@"[MXNativeKeyBackupEngine] megolmBackupAuthDataFromKeyBackupVersion: Key backup for unknown algorithm: %@", keyBackupVersion.algorithm]; + MXLogError(message); + + *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorUnknownAlgorithm + userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Unknown algorithm (%@) for the backup", keyBackupVersion.algorithm] + }]; + + return nil; + } + + return [algorithmClass authDataFromJSON:keyBackupVersion.authData error:error]; +} + +- (NSDictionary *)signObject:(NSDictionary *)object +{ + return [self.crypto signObject:object]; +} + +#pragma mark - Backup keys + +- (BOOL)hasKeysToBackup +{ + return [self.crypto.store inboundGroupSessionsToBackup:1].count > 0; +} + +- (NSProgress *)backupProgress +{ + NSUInteger keys = [self.crypto.store inboundGroupSessionsCount:NO]; + NSUInteger backedUpkeys = [self.crypto.store inboundGroupSessionsCount:YES]; + + NSProgress *progress = [NSProgress progressWithTotalUnitCount:keys]; + progress.completedUnitCount = backedUpkeys; + return progress; +} + +- (MXKeyBackupPayload *)roomKeysBackupPayload +{ + if (!self.keyBackupAlgorithm) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] roomKeysBackupPayload: No known backup algorithm"); + return nil; + } + + // Get a chunk of keys to backup + NSArray *sessions = [self.crypto.store inboundGroupSessionsToBackup:kMXKeyBackupSendKeysMaxCount]; + + MXLogDebug(@"[MXNativeKeyBackupEngine] roomKeysBackupPayload: 1 - %@ sessions to back up", @(sessions.count)); + + // Gather data to send to the homeserver + // roomId -> sessionId -> MXKeyBackupData + NSMutableDictionary *> *roomsKeyBackup = [NSMutableDictionary dictionary]; + + for (MXOlmInboundGroupSession *session in sessions) + { + MXKeyBackupData *keyBackupData = [self.keyBackupAlgorithm encryptGroupSession:session]; + + if (keyBackupData) + { + if (!roomsKeyBackup[session.roomId]) + { + roomsKeyBackup[session.roomId] = [NSMutableDictionary dictionary]; + } + roomsKeyBackup[session.roomId][session.session.sessionIdentifier] = keyBackupData; + } + } + + MXLogDebug(@"[MXNativeKeyBackupEngine] roomKeysBackupPayload: 2 - Finalising data to send"); + + // Finalise data to send + NSMutableDictionary *rooms = [NSMutableDictionary dictionary]; + for (NSString *roomId in roomsKeyBackup) + { + NSMutableDictionary *roomSessions = [NSMutableDictionary dictionary]; + for (NSString *sessionId in roomsKeyBackup[roomId]) + { + roomSessions[sessionId] = roomsKeyBackup[roomId][sessionId]; + } + MXRoomKeysBackupData *roomKeysBackupData = [MXRoomKeysBackupData new]; + roomKeysBackupData.sessions = roomSessions; + + rooms[roomId] = roomKeysBackupData; + } + + MXKeysBackupData *keysBackupData = [MXKeysBackupData new]; + keysBackupData.rooms = rooms; + + MXWeakify(self); + return [[MXKeyBackupPayload alloc] initWithBackupData:keysBackupData + completion:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + [self.crypto.store markBackupDoneForInboundGroupSessions:sessions]; + }]; +} + +- (MXMegolmSessionData *)decryptKeyBackupData:(MXKeyBackupData *)keyBackupData + keyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + privateKey:(NSData *)privateKey + forSession:(NSString *)sessionId + inRoom:(NSString *)roomId +{ + id algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey]; + return [algorithm decryptKeyBackupData:keyBackupData forSession:sessionId inRoom:roomId]; +} + +- (void)importMegolmSessionDatas:(NSArray *)keys + backUp:(BOOL)backUp + success:(void (^)(NSUInteger, NSUInteger))success + failure:(void (^)(NSError * _Nonnull))failure +{ + [self.crypto importMegolmSessionDatas:keys backUp:backUp success:success failure:failure]; +} + +#pragma mark - Private methods - + +- (id)getOrCreateKeyBackupAlgorithmFor:(MXKeyBackupVersion*)keyBackupVersion privateKey:(NSData*)privateKey +{ + if (self.enabled + && [self.keyBackupVersion.JSONDictionary isEqualToDictionary:keyBackupVersion.JSONDictionary] + && [self.privateKey isEqualToData:privateKey]) + { + return self.keyBackupAlgorithm; + } + Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; + if (algorithmClass == NULL) + { + NSString *message = [NSString stringWithFormat:@"[MXNativeKeyBackupEngine] getOrCreateKeyBackupAlgorithmFor: unknown algorithm: %@", keyBackupVersion.algorithm]; + MXLogError(message); + return nil; + } + if (![algorithmClass checkBackupVersion:keyBackupVersion]) + { + MXLogError(@"[MXNativeKeyBackupEngine] getOrCreateKeyBackupAlgorithmFor: invalid backup data returned"); + return nil; + } + NSError *error; + id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; + if (error) + { + MXLogError(@"[MXNativeKeyBackupEngine] getOrCreateKeyBackupAlgorithmFor: invalid auth data"); + return nil; + } + return [[algorithmClass.class alloc] initWithCrypto:self.crypto authData:authData keyGetterBlock:^NSData * _Nullable{ + return privateKey; + }]; +} + +- (OLMPkDecryption*)pkDecryptionFromRecoveryKey:(NSString*)recoveryKey error:(NSError **)error +{ + // Extract the private key + NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:error]; + + // Built the PK decryption with it + OLMPkDecryption *decryption; + if (privateKey) + { + decryption = [OLMPkDecryption new]; + [decryption setPrivateKey:privateKey error:error]; + } + + return decryption; +} + +@end diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h index 99bee89c80..28eba65911 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h @@ -395,11 +395,6 @@ FOUNDATION_EXPORT NSString *const kMXKeyBackupDidStateChangeNotification; */ @property (nonatomic, readonly, nullable) MXKeyBackupVersion *keyBackupVersion; -/** - The backup algorithm being used. Nil if key backup not enabled yet. - */ -@property (nonatomic, readonly, nullable) id keyBackupAlgorithm; - /** Indicate if their are keys to backup. */ diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m index e4cbb12f3d..d372ef2f1a 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m @@ -30,11 +30,8 @@ #import "MXRawDataKey.h" #import "MXCrossSigning_Private.h" #import "MXSharedHistoryKeyService.h" -#import "MXCurve25519BackupAuthData.h" -#import "MXAes256BackupAuthData.h" -#import "MXKeyBackupAlgorithm.h" -#import "MXCurve25519KeyBackupAlgorithm.h" -#import "MXAes256KeyBackupAlgorithm.h" +#import "MXKeyBackupEngine.h" +#import "MatrixSDKSwiftHeader.h" #pragma mark - Constants definitions @@ -45,18 +42,8 @@ */ NSUInteger const kMXKeyBackupWaitingTimeToSendKeyBackup = 10000; -/** - Maximum number of keys to send at a time to the homeserver. - */ -NSUInteger const kMXKeyBackupSendKeysMaxCount = 100; - -static NSDictionary> *AlgorithmClassesByName; -static Class DefaultAlgorithmClass; - @interface MXKeyBackup () { - __weak MXCrypto *crypto; - // The queue to run background tasks dispatch_queue_t cryptoQueue; @@ -67,37 +54,29 @@ @interface MXKeyBackup () void (^backupAllGroupSessionsFailure)(NSError *error); } +@property (nonatomic, strong) id engine; +@property (nonatomic, strong) MXRestClient *restClient; +@property (nonatomic, strong) MXSecretShareManager *secretShareManager; + @end @implementation MXKeyBackup #pragma mark - SDK-Private methods - -+ (void)initialize -{ - if (MXSDKOptions.sharedInstance.enableSymmetricBackup) - { - AlgorithmClassesByName = @{ - kMXCryptoCurve25519KeyBackupAlgorithm: MXCurve25519KeyBackupAlgorithm.class, - kMXCryptoAes256KeyBackupAlgorithm: MXAes256KeyBackupAlgorithm.class - }; - } - else - { - AlgorithmClassesByName = @{ - kMXCryptoCurve25519KeyBackupAlgorithm: MXCurve25519KeyBackupAlgorithm.class, - }; - } - DefaultAlgorithmClass = MXCurve25519KeyBackupAlgorithm.class; -} - -- (instancetype)initWithCrypto:(MXCrypto *)theCrypto +- (instancetype)initWithEngine:(id)engine + restClient:(MXRestClient *)restClient + secretShareManager:(MXSecretShareManager *)secretShareManager + queue:(dispatch_queue_t)queue { self = [self init]; + if (self) { _state = MXKeyBackupStateUnknown; - crypto = theCrypto; - cryptoQueue = crypto.cryptoQueue; + _engine = engine; + _restClient = restClient; + _secretShareManager = secretShareManager; + cryptoQueue = queue; } return self; } @@ -142,45 +121,21 @@ - (void)checkAndStartWithKeyBackupVersion:(nullable MXKeyBackupVersion*)keyBacku return; } - MXKeyBackupVersionTrust *trustInfo = [self trustForKeyBackupVersionFromCryptoQueue:keyBackupVersion]; + MXKeyBackupVersionTrust *trustInfo = [self.engine trustForKeyBackupVersionFromCryptoQueue:keyBackupVersion]; if (trustInfo.usable) { MXLogDebug(@"[MXKeyBackup] checkAndStartWithKeyBackupVersion: Found usable key backup. version: %@", keyBackupVersion.version); // Check the version we used at the previous app run - NSString *versionInStore = crypto.store.backupVersion; + NSString *versionInStore = self.engine.version; if (versionInStore && ![versionInStore isEqualToString:keyBackupVersion.version]) { MXLogDebug(@"[MXKeyBackup] -> clean the previously used version(%@)", versionInStore); [self resetKeyBackupData]; } - - // Check private keys - if (self.hasPrivateKeyInCryptoStore) - { - Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; - if (algorithmClass == NULL) - { - NSString *message = [NSString stringWithFormat:@"[MXKeyBackup] checkAndStartWithKeyBackupVersion: unknown algorithm: %@", keyBackupVersion.algorithm]; - MXLogError(message); - return; - } - if (![algorithmClass checkBackupVersion:keyBackupVersion]) - { - MXLogError(@"[MXKeyBackup] checkAndStartWithKeyBackupVersion: invalid backup data returned"); - return; - } - NSData *privateKey = self.privateKeyFromCryptoStore; - NSError *error; - BOOL keyMatches = [algorithmClass keyMatches:privateKey withAuthData:keyBackupVersion.authData error:&error]; - if (error || !keyMatches) - { - MXLogDebug(@"[MXKeyBackup] checkAndStartWithKeyBackupVersion: -> private key does not match: %@, will be removed", error); - [crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; - } - } + [self.engine validateKeyBackupVersion:keyBackupVersion]; MXLogDebug(@"[MXKeyBackup] -> enabling key backups"); [self enableKeyBackup:keyBackupVersion]; @@ -189,7 +144,7 @@ - (void)checkAndStartWithKeyBackupVersion:(nullable MXKeyBackupVersion*)keyBacku { MXLogDebug(@"[MXKeyBackup] checkAndStartWithKeyBackupVersion: No usable key backup. version: %@", keyBackupVersion.version); - if (crypto.store.backupVersion) + if (self.engine.version) { MXLogDebug(@"[MXKeyBackup] -> disable the current version"); [self resetKeyBackupData]; @@ -208,24 +163,16 @@ - (void)checkAndStartWithKeyBackupVersion:(nullable MXKeyBackupVersion*)keyBacku - (NSError*)enableKeyBackup:(MXKeyBackupVersion*)version { NSError *error; - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:version error:&error]; - if (!error) + if (![self.engine enableBackupWithVersion:version error:&error]) { - _keyBackupVersion = version; - crypto.store.backupVersion = version.version; - Class algorithmClass = AlgorithmClassesByName[version.algorithm]; - // store the desired backup algorithm - _keyBackupAlgorithm = [[algorithmClass alloc] initWithCrypto:crypto authData:authData keyGetterBlock:^NSData * _Nullable{ - return self.privateKeyFromCryptoStore; - }]; - MXLogDebug(@"[MXKeyBackup] Algorithm set to: %@", _keyBackupAlgorithm); - - self.state = MXKeyBackupStateReadyToBackUp; - - [self maybeSendKeyBackup]; + return error; } + _keyBackupVersion = version; + self.state = MXKeyBackupStateReadyToBackUp; + + [self maybeSendKeyBackup]; - return error; + return nil; } - (void)resetKeyBackupData @@ -233,13 +180,7 @@ - (void)resetKeyBackupData MXLogDebug(@"[MXKeyBackup] resetKeyBackupData"); [self resetBackupAllGroupSessionsObjects]; - - self->crypto.store.backupVersion = nil; - [self->crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; - _keyBackupAlgorithm = nil; - - // Reset backup markers - [self->crypto.store resetBackupMarkers]; + [self.engine disableBackup]; } - (void)maybeSendKeyBackup @@ -275,13 +216,8 @@ - (void)maybeSendKeyBackup - (void)sendKeyBackup { MXLogDebug(@"[MXKeyBackup] sendKeyBackup"); - - // Get a chunk of keys to backup - NSArray *sessions = [crypto.store inboundGroupSessionsToBackup:kMXKeyBackupSendKeysMaxCount]; - - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 1 - %@ sessions to back up", @(sessions.count)); - - if (!sessions.count) + + if (!self.engine.hasKeysToBackup) { // Backup is up to date self.state = MXKeyBackupStateReadyToBackUp; @@ -295,7 +231,7 @@ - (void)sendKeyBackup } // Sanity check - if (!self.enabled || !_keyBackupAlgorithm || !_keyBackupVersion) + if (!self.enabled || !_keyBackupVersion) { MXLogDebug(@"[MXKeyBackup] sendKeyBackup: Invalid state: %@", @(_state)); if (backupAllGroupSessionsFailure) @@ -313,59 +249,27 @@ - (void)sendKeyBackup self.state = MXKeyBackupStateBackingUp; MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 2 - Encrypting keys"); - - // Gather data to send to the homeserver - // roomId -> sessionId -> MXKeyBackupData - NSMutableDictionary *> *roomsKeyBackup = [NSMutableDictionary dictionary]; - - for (MXOlmInboundGroupSession *session in sessions) - { - MXKeyBackupData *keyBackupData = [_keyBackupAlgorithm encryptGroupSession:session]; - - if (keyBackupData) - { - if (!roomsKeyBackup[session.roomId]) - { - roomsKeyBackup[session.roomId] = [NSMutableDictionary dictionary]; - } - roomsKeyBackup[session.roomId][session.session.sessionIdentifier] = keyBackupData; - } - } - - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 3 - Finalising data to send"); - - // Finalise data to send - NSMutableDictionary *rooms = [NSMutableDictionary dictionary]; - for (NSString *roomId in roomsKeyBackup) + + MXKeyBackupPayload *payload = [self.engine roomKeysBackupPayload]; + if (!payload) { - NSMutableDictionary *roomSessions = [NSMutableDictionary dictionary]; - for (NSString *sessionId in roomsKeyBackup[roomId]) - { - roomSessions[sessionId] = roomsKeyBackup[roomId][sessionId]; - } - MXRoomKeysBackupData *roomKeysBackupData = [MXRoomKeysBackupData new]; - roomKeysBackupData.sessions = roomSessions; - - rooms[roomId] = roomKeysBackupData; + MXLogError(@"[MXKeyBackup] sendKeyBackup: Cannot get room key backups"); + return; } - MXKeysBackupData *keysBackupData = [MXKeysBackupData new]; - keysBackupData.rooms = rooms; - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 4 - Sending request"); // Make the request MXWeakify(self); - [crypto.matrixRestClient sendKeysBackup:keysBackupData version:_keyBackupVersion.version success:^{ + [self.restClient sendKeysBackup:payload.backupData version:_keyBackupVersion.version success:^(NSDictionary *JSONResponse){ MXStrongifyAndReturnIfNil(self); MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 5a - Request complete"); // Mark keys as backed up - [self->crypto.store markBackupDoneForInboundGroupSessions:sessions]; + payload.completion(JSONResponse); - if (sessions.count < kMXKeyBackupSendKeysMaxCount) + if (!self.engine.hasKeysToBackup) { MXLogDebug(@"[MXKeyBackup] sendKeyBackup: All keys have been backed up"); self.state = MXKeyBackupStateReadyToBackUp; @@ -440,7 +344,7 @@ - (void)restoreKeyBackupAutomaticallyWithPrivateKey:(void (^)(void))onComplete } // Check private keys - if (!self.hasPrivateKeyInCryptoStore) + if (!self.engine.privateKey) { MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error: No private key"); onComplete(); @@ -448,13 +352,13 @@ - (void)restoreKeyBackupAutomaticallyWithPrivateKey:(void (^)(void))onComplete } // Check private keys validity - NSData *privateKey = self.privateKeyFromCryptoStore; + NSData *privateKey = self.engine.privateKey; NSError *error; - BOOL keyMatches = [_keyBackupAlgorithm keyMatches:privateKey error:&error]; - if (error || !keyMatches) + BOOL keyMatches = [self.engine isValidPrivateKey:privateKey error:&error]; + if (!keyMatches) { MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error: Private key does not match: %@", error); - [crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; + [self.engine deletePrivateKey]; onComplete(); return; } @@ -497,7 +401,7 @@ - (MXHTTPOperation *)version:(NSString *)version success:(void (^)(MXKeyBackupVe - (MXHTTPOperation *)versionFromCryptoQueue:(NSString *)version success:(void (^)(MXKeyBackupVersion * _Nullable))success failure:(void (^)(NSError * _Nonnull))failure { - return [crypto.matrixRestClient keyBackupVersion:version success:success failure:^(NSError *error) { + return [self.restClient keyBackupVersion:version success:success failure:^(NSError *error) { // Workaround because the homeserver currently returns M_NOT_FOUND when there is // no key backup @@ -524,84 +428,7 @@ - (void)prepareKeyBackupVersionWithPassword:(NSString *)password MXWeakify(self); dispatch_async(cryptoQueue, ^{ MXStrongifyAndReturnIfNil(self); - - Class algorithmClass = algorithm ? AlgorithmClassesByName[algorithm] : DefaultAlgorithmClass; - if (algorithmClass == NULL) - { - if (failure) - { - dispatch_async(dispatch_get_main_queue(), ^{ - NSError *error = [NSError errorWithDomain:MXKeyBackupErrorDomain - code:MXKeyBackupErrorUnknownAlgorithm - userInfo:@{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Unknown algorithm (%@) to prepare the backup", algorithm] - }]; - failure(error); - }); - } - return; - } - NSError *error; - MXKeyBackupPreparationInfo *preparationInfo = [algorithmClass prepareWith:password error:&error]; - if (error) - { - if (failure) - { - dispatch_async(dispatch_get_main_queue(), ^{ - failure(error); - }); - } - return; - } - id authData = preparationInfo.authData; - - MXMegolmBackupCreationInfo *keyBackupCreationInfo = [MXMegolmBackupCreationInfo new]; - keyBackupCreationInfo.algorithm = [algorithmClass algorithmName]; - keyBackupCreationInfo.authData = authData; - keyBackupCreationInfo.recoveryKey = [MXRecoveryKey encode:preparationInfo.privateKey]; - - NSString *myUserId = self->crypto.matrixRestClient.credentials.userId; - NSMutableDictionary *signatures = [NSMutableDictionary dictionary]; - - NSDictionary *deviceSignature = [self->crypto signObject:authData.signalableJSONDictionary]; - [signatures addEntriesFromDictionary:deviceSignature[myUserId]]; - - if ([self->crypto.crossSigning canCrossSign] == NO) - { - authData.signatures = @{myUserId: signatures}; - keyBackupCreationInfo.authData = authData; - - if (success) - { - dispatch_async(dispatch_get_main_queue(), ^{ - success(keyBackupCreationInfo); - }); - } - - return; - } - - [self->crypto.crossSigning signObject:authData.signalableJSONDictionary withKeyType:MXCrossSigningKeyType.master success:^(NSDictionary *signedObject) { - - [signatures addEntriesFromDictionary:signedObject[@"signatures"][myUserId]]; - - authData.signatures = @{myUserId: signatures}; - keyBackupCreationInfo.authData = authData; - - if (success) - { - dispatch_async(dispatch_get_main_queue(), ^{ - success(keyBackupCreationInfo); - }); - } - } failure:^(NSError *error) { - if (failure) - { - dispatch_async(dispatch_get_main_queue(), ^{ - failure(error); - }); - } - }]; + [self.engine prepareKeyBackupVersionWithPassword:password algorithm:algorithm success:success failure:failure]; }); } @@ -621,13 +448,13 @@ - (MXHTTPOperation*)createKeyBackupVersion:(MXMegolmBackupCreationInfo*)keyBacku keyBackupVersion.algorithm = keyBackupCreationInfo.algorithm; keyBackupVersion.authData = keyBackupCreationInfo.authData.JSONDictionary; - MXHTTPOperation *operation2 = [self->crypto.matrixRestClient createKeyBackupVersion:keyBackupVersion success:^(NSString *version) { + MXHTTPOperation *operation2 = [self.restClient createKeyBackupVersion:keyBackupVersion success:^(NSString *version) { + + // Disable current backup + [self.engine disableBackup]; // Store the fresh new private key - [self storePrivateKeyWithRecoveryKey:keyBackupCreationInfo.recoveryKey]; - - // Reset backup markers - [self->crypto.store resetBackupMarkers]; + [self.engine saveRecoveryKey:keyBackupCreationInfo.recoveryKey]; keyBackupVersion.version = version; @@ -679,7 +506,7 @@ - (MXHTTPOperation*)deleteKeyBackupVersion:(NSString*)version } MXWeakify(self); - MXHTTPOperation *operation2 = [self->crypto.matrixRestClient deleteKeyBackupVersion:version success:^{ + MXHTTPOperation *operation2 = [self.restClient deleteKeyBackupVersion:version success:^{ MXStrongifyAndReturnIfNil(self); // Do not stay in MXKeyBackupStateUnknown but check what is available on the homeserver @@ -874,13 +701,8 @@ - (void)backupProgress:(void (^)(NSProgress *backupProgress))backupProgress MXWeakify(self); dispatch_async(cryptoQueue, ^{ MXStrongifyAndReturnIfNil(self); - - NSUInteger keys = [self->crypto.store inboundGroupSessionsCount:NO]; - NSUInteger backedUpkeys = [self->crypto.store inboundGroupSessionsCount:YES]; - - NSProgress *progress = [NSProgress progressWithTotalUnitCount:keys]; - progress.completedUnitCount = backedUpkeys; - + + NSProgress *progress = self.engine.backupProgress; dispatch_async(dispatch_get_main_queue(), ^{ backupProgress(progress); }); @@ -915,7 +737,7 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion // Check if the recovery is valid before going any further NSError *error; - BOOL isValidRecoveryKey = [self isValidRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; + BOOL isValidRecoveryKey = [self.engine isValidRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:&error]; if (error || !isValidRecoveryKey || !privateKey) { @@ -934,7 +756,7 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion // Catch the private key from the recovery key and store it locally if ([self.keyBackupVersion.version isEqualToString:keyBackupVersion.version]) { - [self storePrivateKeyWithRecoveryKey:recoveryKey]; + [self.engine saveRecoveryKey:recoveryKey]; } if (success) @@ -971,9 +793,11 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion { sessionsFromHSCount++; MXKeyBackupData *keyBackupData = keysBackupData.rooms[roomId].sessions[sessionId]; - - id algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey]; - MXMegolmSessionData *sessionData = [algorithm decryptKeyBackupData:keyBackupData forSession:sessionId inRoom:roomId]; + MXMegolmSessionData *sessionData = [self.engine decryptKeyBackupData:keyBackupData + keyBackupVersion:keyBackupVersion + privateKey:privateKey + forSession:sessionId + inRoom:roomId]; if (sessionData) { @@ -992,7 +816,7 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion } // Import them into the crypto store - [self->crypto importMegolmSessionDatas:sessionDatas backUp:backUp success:success failure:^(NSError *error) { + [self.engine importMegolmSessionDatas:sessionDatas backUp:backUp success:success failure:^(NSError *error) { if (failure) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -1028,7 +852,7 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion // Retrieve the private key from the password NSError *error; - NSString *recoveryKey = [self recoveryKeyFromPassword:password inKeyBackupVersion:keyBackupVersion error:&error]; + NSString *recoveryKey = [self.engine recoveryKeyFromPassword:password inKeyBackupVersion:keyBackupVersion error:&error]; if (!error) { @@ -1063,9 +887,9 @@ - (MXHTTPOperation*)restoreUsingPrivateKeyKeyBackup:(MXKeyBackupVersion*)keyBack dispatch_async(cryptoQueue, ^{ MXStrongifyAndReturnIfNil(self); - NSData *privateKey = self.privateKeyFromCryptoStore; + NSData *privateKey = self.engine.privateKey; NSError *error; - if (error || ![[self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey] keyMatches:privateKey error:&error]) + if (![self.engine isValidPrivateKey:privateKey forKeyBackupVersion:keyBackupVersion error:&error]) { MXLogDebug(@"[MXKeyBackup] restoreUsingPrivateKeyKeyBackup. Error: Private key does not match: %@, for: %@", error, keyBackupVersion); if (failure) @@ -1091,17 +915,16 @@ - (MXHTTPOperation*)restoreUsingPrivateKeyKeyBackup:(MXKeyBackupVersion*)keyBack - (BOOL)hasPrivateKeyInCryptoStore { - return [crypto.store secretWithSecretId:MXSecretId.keyBackup] != nil; + return self.engine.privateKey != nil; } - #pragma mark - Backup trust - (void)trustForKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion onComplete:(void (^)(MXKeyBackupVersionTrust * _Nonnull))onComplete { dispatch_async(cryptoQueue, ^{ - MXKeyBackupVersionTrust *keyBackupVersionTrust = [self trustForKeyBackupVersionFromCryptoQueue:keyBackupVersion]; + MXKeyBackupVersionTrust *keyBackupVersionTrust = [self.engine trustForKeyBackupVersionFromCryptoQueue:keyBackupVersion]; dispatch_async(dispatch_get_main_queue(), ^{ onComplete(keyBackupVersionTrust); @@ -1109,98 +932,6 @@ - (void)trustForKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion onComple }); } -- (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBackupVersion *)keyBackupVersion -{ - NSString *myUserId = crypto.matrixRestClient.credentials.userId; - - MXKeyBackupVersionTrust *keyBackupVersionTrust = [MXKeyBackupVersionTrust new]; - - NSError *error; - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; - if (error) - { - MXLogDebug(@"[MXKeyBackup] trustForKeyBackupVersion: Key backup is absent or missing required data"); - return keyBackupVersionTrust; - } - - NSData *privateKey = self.privateKeyFromCryptoStore; - if (privateKey) - { - id 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 *signatures = [NSMutableArray array]; - for (NSString *keyId in mySigs) - { - // XXX: is this how we're supposed to get the device id? - NSString *deviceId; - NSArray *components = [keyId componentsSeparatedByString:@":"]; - if (components.count == 2) - { - deviceId = components[1]; - } - - if (deviceId) - { - BOOL valid = NO; - - MXDeviceInfo *device = [self->crypto.deviceList storedDevice:myUserId deviceId:deviceId]; - if (device) - { - NSError *error; - valid = [self->crypto.olmDevice verifySignature:device.fingerprint JSON:authData.signalableJSONDictionary signature:mySigs[keyId] error:&error]; - - if (!valid) - { - MXLogDebug(@"[MXKeyBackup] trustForKeyBackupVersion: Bad signature from device %@: %@", device.deviceId, error); - } - - MXKeyBackupVersionTrustSignature *signature = [MXKeyBackupVersionTrustSignature new]; - signature.deviceId = deviceId; - signature.device = device; - signature.valid = valid; - [signatures addObject:signature]; - } - else // Try interpreting it as the MSK public key - { - NSError *error; - BOOL valid = [crypto.crossSigning.crossSigningTools pkVerifyObject:authData.JSONDictionary userId:myUserId publicKey:deviceId error:&error]; - - if (!valid) - { - MXLogDebug(@"[MXKeyBackup] trustForKeyBackupVersion: Signature with unknown key %@", deviceId); - } - else - { - MXKeyBackupVersionTrustSignature *signature = [MXKeyBackupVersionTrustSignature new]; - signature.keys = deviceId; - signature.valid = valid; - [signatures addObject:signature]; - } - } - } - } - - keyBackupVersionTrust.signatures = signatures; - - for (MXKeyBackupVersionTrustSignature *signature in keyBackupVersionTrust.signatures) - { - if (signature.valid && signature.device && signature.device.trustLevel.isVerified) - { - keyBackupVersionTrust.usable = YES; - } - } - keyBackupVersionTrust.usable = keyBackupVersionTrust.usable || keyBackupVersionTrust.isTrustedLocally; - - return keyBackupVersionTrust; -} - - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion trust:(BOOL)trust success:(void (^)(void))success @@ -1214,11 +945,11 @@ - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersio dispatch_async(cryptoQueue, ^{ MXStrongifyAndReturnIfNil(self); - NSString *myUserId = self->crypto.matrixRestClient.credentials.userId; + NSString *myUserId = self.restClient.credentials.userId; // Get auth data to update it NSError *error; - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; + id authData = [self.engine megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; if (error) { MXLogDebug(@"[MXKeyBackup] trustKeyBackupVersion:trust: Key backup is missing required data"); @@ -1246,12 +977,12 @@ - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersio // Add or remove current device signature if (trust) { - NSDictionary *deviceSignatures = [self->crypto signObject:authData.signalableJSONDictionary][myUserId]; + NSDictionary *deviceSignatures = [self.engine signObject:authData.signalableJSONDictionary][myUserId]; [myUserSignatures addEntriesFromDictionary:deviceSignatures]; } else { - NSString *myDeviceId = self->crypto.store.deviceId; + NSString *myDeviceId = self.restClient.credentials.deviceId; NSString *deviceSignKeyId = [NSString stringWithFormat:@"ed25519:%@", myDeviceId]; [myUserSignatures removeObjectForKey:deviceSignKeyId]; } @@ -1265,7 +996,7 @@ - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersio newKeyBackupVersion.authData = authData.JSONDictionary; // And send it to the homeserver - MXHTTPOperation *operation2 = [self->crypto.matrixRestClient updateKeyBackupVersion:newKeyBackupVersion success:^(void) { + MXHTTPOperation *operation2 = [self.restClient updateKeyBackupVersion:newKeyBackupVersion success:^(void) { // Relaunch the state machine on this updated backup version [self checkAndStartWithKeyBackupVersion:newKeyBackupVersion]; @@ -1306,7 +1037,7 @@ - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersio MXStrongifyAndReturnIfNil(self); NSError *error; - [self isValidRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; + [self.engine isValidRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; if (!error) { MXHTTPOperation *operation2 = [self trustKeyBackupVersion:keyBackupVersion trust:YES success:success failure:failure]; @@ -1342,7 +1073,7 @@ - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersio MXStrongifyAndReturnIfNil(self); NSError *error; - NSString *recoveryKey = [self recoveryKeyFromPassword:password inKeyBackupVersion:keyBackupVersion error:&error]; + NSString *recoveryKey = [self.engine recoveryKeyFromPassword:password inKeyBackupVersion:keyBackupVersion error:&error]; if (!error) { @@ -1375,7 +1106,7 @@ - (void)requestPrivateKeysToDeviceIds:(nullable NSArray*)deviceIds MXLogDebug(@"[MXKeyBackup] requestPrivateKeysToDeviceIds: %@", deviceIds); MXWeakify(self); - [crypto.secretShareManager requestSecret:MXSecretId.keyBackup toDeviceIds:deviceIds success:^(NSString * _Nonnull requestId) { + [self.secretShareManager requestSecret:MXSecretId.keyBackup toDeviceIds:deviceIds success:^(NSString * _Nonnull requestId) { } onSecretReceived:^BOOL(NSString * _Nonnull secret) { MXStrongifyAndReturnValueIfNil(self, NO); @@ -1385,7 +1116,7 @@ - (void)requestPrivateKeysToDeviceIds:(nullable NSArray*)deviceIds MXLogDebug(@"[MXKeyBackup] requestPrivateKeysToDeviceIds: Got key. isSecretValid: %@", @(isSecretValid)); if (isSecretValid) { - [self->crypto.store storeSecret:secret withSecretId:MXSecretId.keyBackup]; + [self.engine savePrivateKey:secret]; onPrivateKeysReceived(); } return isSecretValid; @@ -1395,20 +1126,19 @@ - (void)requestPrivateKeysToDeviceIds:(nullable NSArray*)deviceIds - (BOOL)isSecretValid:(NSString*)secret forKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion { NSData *privateKey = [MXBase64Tools dataFromBase64:secret]; - id algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey]; - return [algorithm keyMatches:privateKey error:nil]; + return [self.engine isValidPrivateKey:privateKey forKeyBackupVersion:keyBackupVersion error:nil]; } #pragma mark - Backup state - (BOOL)enabled { - return _state >= MXKeyBackupStateReadyToBackUp; + return _state >= MXKeyBackupStateReadyToBackUp && self.engine.enabled; } - (BOOL)hasKeysToBackup { - return [crypto.store inboundGroupSessionsToBackup:1].count > 0; + return [self.engine hasKeysToBackup]; } - (BOOL)canBeRefreshed @@ -1418,38 +1148,6 @@ - (BOOL)canBeRefreshed #pragma mark - Private methods - -- (id)getOrCreateKeyBackupAlgorithmFor:(MXKeyBackupVersion*)keyBackupVersion privateKey:(NSData*)privateKey -{ - if (self.enabled - && [_keyBackupVersion.JSONDictionary isEqualToDictionary:keyBackupVersion.JSONDictionary] - && [self.privateKeyFromCryptoStore isEqualToData:privateKey]) - { - return _keyBackupAlgorithm; - } - Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; - if (algorithmClass == NULL) - { - NSString *message = [NSString stringWithFormat:@"[MXKeyBackup] getOrCreateKeyBackupAlgorithmFor: unknown algorithm: %@", keyBackupVersion.algorithm]; - MXLogError(message); - return nil; - } - if (![algorithmClass checkBackupVersion:keyBackupVersion]) - { - MXLogError(@"[MXKeyBackup] getOrCreateKeyBackupAlgorithmFor: invalid backup data returned"); - return nil; - } - NSError *error; - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; - if (error) - { - MXLogError(@"[MXKeyBackup] getOrCreateKeyBackupAlgorithmFor: invalid auth data"); - return nil; - } - return [[algorithmClass.class alloc] initWithCrypto:crypto authData:authData keyGetterBlock:^NSData * _Nullable{ - return privateKey; - }]; -} - - (void)setState:(MXKeyBackupState)state { MXLogDebug(@"[MXKeyBackup] setState: %@ -> %@", @(_state), @(state)); @@ -1473,11 +1171,11 @@ - (MXHTTPOperation*)keyBackupForSession:(nullable NSString*)sessionId if (!sessionId && !roomId) { - operation = [crypto.matrixRestClient keysBackup:version success:success failure:failure]; + operation = [self.restClient keysBackup:version success:success failure:failure]; } else if (!sessionId) { - operation = [crypto.matrixRestClient keysBackupInRoom:roomId version:version success:^(MXRoomKeysBackupData *roomKeysBackupData) { + operation = [self.restClient keysBackupInRoom:roomId version:version success:^(MXRoomKeysBackupData *roomKeysBackupData) { MXKeysBackupData *keysBackupData = [MXKeysBackupData new]; keysBackupData.rooms = @{ @@ -1490,7 +1188,7 @@ - (MXHTTPOperation*)keyBackupForSession:(nullable NSString*)sessionId } else { - operation = [crypto.matrixRestClient keyBackupForSession:sessionId inRoom:roomId version:version success:^(MXKeyBackupData *keyBackupData) { + operation = [self.restClient keyBackupForSession:sessionId inRoom:roomId version:version success:^(MXKeyBackupData *keyBackupData) { MXRoomKeysBackupData *roomKeysBackupData = [MXRoomKeysBackupData new]; roomKeysBackupData.sessions = @{ @@ -1510,166 +1208,4 @@ - (MXHTTPOperation*)keyBackupForSession:(nullable NSString*)sessionId return operation; } -- (OLMPkDecryption*)pkDecryptionFromRecoveryKey:(NSString*)recoveryKey error:(NSError **)error -{ - // Extract the private key - NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:error]; - - // Built the PK decryption with it - OLMPkDecryption *decryption; - if (privateKey) - { - decryption = [OLMPkDecryption new]; - [decryption setPrivateKey:privateKey error:error]; - } - - return decryption; -} - -- (void)storePrivateKeyWithRecoveryKey:(NSString*)recoveryKey -{ - NSError *error; - OLMPkDecryption *decryption = [self pkDecryptionFromRecoveryKey:recoveryKey error:&error]; - if (!decryption) - { - MXLogDebug(@"[MXKeyBackup] storePrivateKeyWithRecoveryKey] Cannot create OLMPkDecryption. Error: %@", error); - return; - } - - NSString *privateKeyBase64 = [MXBase64Tools unpaddedBase64FromData:decryption.privateKey]; - [crypto.store storeSecret:privateKeyBase64 withSecretId:MXSecretId.keyBackup]; -} - -- (nullable NSData*)privateKeyFromCryptoStore -{ - NSString *privateKeyBase64 = [crypto.store secretWithSecretId:MXSecretId.keyBackup]; - if (!privateKeyBase64) - { - MXLogDebug(@"[MXKeyBackup] privateKeyFromCryptoStore. Error: No secret in crypto store"); - return nil; - } - - return [MXBase64Tools dataFromBase64:privateKeyBase64]; -} - -/** - Extract megolm back up authentication data from a backup. - - @param keyBackupVersion the key backup - @param error the encountered error in case of failure. - @return the authentication if found and valid. - */ -- (nullable id)megolmBackupAuthDataFromKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion error:(NSError**)error -{ - Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; - if (algorithmClass == NULL) - { - NSString *message = [NSString stringWithFormat:@"[MXKeyBackup] megolmBackupAuthDataFromKeyBackupVersion: Key backup for unknown algorithm: %@", keyBackupVersion.algorithm]; - MXLogError(message); - - *error = [NSError errorWithDomain:MXKeyBackupErrorDomain - code:MXKeyBackupErrorUnknownAlgorithm - userInfo:@{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Unknown algorithm (%@) for the backup", keyBackupVersion.algorithm] - }]; - - return nil; - } - - return [algorithmClass authDataFromJSON:keyBackupVersion.authData error:error]; -} - -/** - Compute the recovery key from a password and key backup auth data. - - @param password the password. - @param keyBackupVersion the backup and its auth data. - @param error the encountered error in case of failure. - @return the recovery key if successful. - */ -- (nullable NSString*)recoveryKeyFromPassword:(NSString*)password inKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion error:(NSError **)error -{ - // Extract MXBaseKeyBackupAuthData - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:error]; - if (*error) - { - return nil; - } - - if (!authData.privateKeySalt || !authData.privateKeyIterations) - { - MXLogDebug(@"[MXKeyBackup] recoveryFromPassword: Salt and/or iterations not found in key backup auth data"); - *error = [NSError errorWithDomain:MXKeyBackupErrorDomain - code:MXKeyBackupErrorMissingPrivateKeySaltCode - userInfo:@{ - NSLocalizedDescriptionKey: @"Salt and/or iterations not found in key backup auth data" - }]; - return nil; - } - - - // Extract the recovery key from the passphrase - NSData *recoveryKeyData = [MXKeyBackupPassword retrievePrivateKeyWithPassword:password salt:authData.privateKeySalt iterations:authData.privateKeyIterations error:error]; - if (*error) - { - MXLogDebug(@"[MXKeyBackup] recoveryFromPassword: retrievePrivateKeyWithPassword failed: %@", *error); - return nil; - } - - return [MXRecoveryKey encode:recoveryKeyData]; -} - -/** - Check if a recovery key matches key backup authentication data. - - @param recoveryKey the recovery key to challenge. - @param keyBackupVersion the backup and its auth data. - @param error the encountered error in case of failure. - @return YES if successful. - */ -- (BOOL)isValidRecoveryKey:(NSString*)recoveryKey forKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion error:(NSError **)error -{ - NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:error]; - - if (*error) - { - MXLogDebug(@"[MXKeyBackup] isValidRecoveryKey: Invalid recovery key. Error: %@", *error); - - // Return a generic error - *error = [NSError errorWithDomain:MXKeyBackupErrorDomain - code:MXKeyBackupErrorInvalidRecoveryKeyCode - userInfo:@{ - NSLocalizedDescriptionKey: @"Invalid recovery key or password" - }]; - return NO; - } - - Class algorithm = AlgorithmClassesByName[keyBackupVersion.algorithm]; - if (algorithm == NULL) - { - MXLogDebug(@"[MXKeyBackup] isValidRecoveryKey: unknown algorithm: %@", keyBackupVersion.algorithm); - - *error = [NSError errorWithDomain:MXKeyBackupErrorDomain - code:MXKeyBackupErrorUnknownAlgorithm - userInfo:@{ - NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Unknown algorithm (%@)", keyBackupVersion.algorithm] - }]; - return NO; - } - BOOL result = [algorithm keyMatches:privateKey withAuthData:keyBackupVersion.authData error:error]; - - if (!result) - { - MXLogDebug(@"[MXKeyBackup] isValidRecoveryKey: Public keys mismatch"); - - *error = [NSError errorWithDomain:MXKeyBackupErrorDomain - code:MXKeyBackupErrorInvalidRecoveryKeyCode - userInfo:@{ - NSLocalizedDescriptionKey: @"Invalid recovery key or password: public keys mismatch" - }]; - } - - return result; -} - @end diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h index aafd195bde..af24a8767d 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h @@ -15,6 +15,8 @@ */ #import "MXKeyBackup.h" +#import "MXKeyBackupEngine.h" +#import "MXSecretShareManager.h" @class MXCrypto; @@ -28,9 +30,15 @@ NS_ASSUME_NONNULL_BEGIN /** Constructor. - @param crypto the related 'MXCrypto'. + @param engine backup engine that stores and manages keys + @param restClient rest client to perform http requests + @param secretShareManager manages of secrets hsaring + @param queue dispatch queue to perform all operations on */ -- (instancetype)initWithCrypto:(MXCrypto *)crypto; +- (instancetype)initWithEngine:(id)engine + restClient:(MXRestClient *)restClient + secretShareManager:(MXSecretShareManager *)secretShareManager + queue:(dispatch_queue_t)queue; /** Check the server for an active key backup. diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index 14fbb4eb91..a3472b9566 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -53,6 +53,8 @@ #import "MatrixSDKSwiftHeader.h" #import "MXSharedHistoryKeyService.h" +#import "MXNativeKeyBackupEngine.h" + /** The store to use for crypto. */ @@ -2032,11 +2034,6 @@ - (instancetype)initWithMatrixSession:(MXSession*)matrixSession cryptoQueue:(dis oneTimeKeyCount = -1; - if ([MXSDKOptions sharedInstance].enableKeyBackupWhenStartingMXCrypto) - { - _backup = [[MXKeyBackup alloc] initWithCrypto:self]; - } - outgoingRoomKeyRequestManager = [[MXOutgoingRoomKeyRequestManager alloc] initWithMatrixRestClient:_matrixRestClient deviceId:_myDevice.deviceId @@ -2054,6 +2051,15 @@ - (instancetype)initWithMatrixSession:(MXSession*)matrixSession cryptoQueue:(dis _recoveryService = [[MXRecoveryService alloc] initWithCrypto:self]; + if ([MXSDKOptions sharedInstance].enableKeyBackupWhenStartingMXCrypto) + { + id engine = [[MXNativeKeyBackupEngine alloc] initWithCrypto:self]; + _backup = [[MXKeyBackup alloc] initWithEngine:engine + restClient:_matrixRestClient + secretShareManager:_secretShareManager + queue:_cryptoQueue]; + } + cryptoMigration = [[MXCryptoMigration alloc] initWithCrypto:self]; lastNewSessionForcedDates = [MXUsersDevicesMap new]; diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index 2f37ed01dd..e430595b14 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -2554,7 +2554,7 @@ Note: Clients should consider avoiding this endpoint for URLs posted in encrypte */ - (MXHTTPOperation*)sendKeysBackup:(MXKeysBackupData*)keysBackupData version:(NSString*)version - success:(void (^)(void))success + success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure; /** diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index d558770185..94f3a7ce80 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -5026,7 +5026,9 @@ - (MXHTTPOperation*)sendKeyBackup:(MXKeyBackupData*)keyBackupData return nil; } - return [self sendBackup:keyBackupData.JSONDictionary path:path success:success failure:failure]; + return [self sendBackup:keyBackupData.JSONDictionary path:path success:^(NSDictionary *JSONResponse) { + success(); + } failure:failure]; } - (MXHTTPOperation*)sendRoomKeysBackup:(MXRoomKeysBackupData*)roomKeysBackupData @@ -5043,12 +5045,14 @@ - (MXHTTPOperation*)sendRoomKeysBackup:(MXRoomKeysBackupData*)roomKeysBackupData return nil; } - return [self sendBackup:roomKeysBackupData.JSONDictionary path:path success:success failure:failure]; + return [self sendBackup:roomKeysBackupData.JSONDictionary path:path success:^(NSDictionary *JSONResponse) { + success(); + } failure:failure]; } - (MXHTTPOperation*)sendKeysBackup:(MXKeysBackupData*)keysBackupData version:(NSString*)version - success:(void (^)(void))success + success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure { NSString *path = [self keyBackupPath:nil session:nil version:version]; @@ -5064,7 +5068,7 @@ - (MXHTTPOperation*)sendKeysBackup:(MXKeysBackupData*)keysBackupData - (MXHTTPOperation*)sendBackup:(NSDictionary*)backupData path:(NSString*)path - success:(void (^)(void))success + success:(void (^)(NSDictionary *JSONResponse))success failure:(void (^)(NSError *error))failure { MXWeakify(self); @@ -5078,7 +5082,7 @@ - (MXHTTPOperation*)sendBackup:(NSDictionary*)backupData { [self dispatchProcessing:nil andCompletion:^{ - success(); + success(JSONResponse); }]; } } failure:^(NSError *error) { diff --git a/MatrixSDKTests/MXBaseKeyBackupTests.m b/MatrixSDKTests/MXBaseKeyBackupTests.m index 4f170bda8d..22173e0585 100644 --- a/MatrixSDKTests/MXBaseKeyBackupTests.m +++ b/MatrixSDKTests/MXBaseKeyBackupTests.m @@ -24,6 +24,7 @@ #import "MXCrossSigning_Private.h" #import "MXKeyBackupAlgorithm.h" #import "MXAes256BackupAuthData.h" +#import "MXNativeKeyBackupEngine.h" @implementation MXBaseKeyBackupTests @@ -612,14 +613,23 @@ - (void)testEncryptAndDecryptKeyBackupData [aliceSession.crypto.backup prepareKeyBackupVersionWithPassword:nil algorithm:self.algorithm success:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { - + + // This test relies on internal implementation detail (keyBackupAlgorithm class) only available with crypto v1. + // When run as V2 this test should fail until a better test is written + id engine = [aliceSession.crypto.backup valueForKey:@"engine"]; + if (!engine || ![engine isKindOfClass:[MXNativeKeyBackupEngine class]]) { + XCTFail(@"Cannot verify test"); + [expectation fulfill]; + } + id keyBackupAlgorithm = ((MXNativeKeyBackupEngine *)engine).keyBackupAlgorithm; + // - Check [MXKeyBackupAlgorithm encryptGroupSession] returns stg - MXKeyBackupData *keyBackupData = [aliceSession.crypto.backup.keyBackupAlgorithm encryptGroupSession:session]; + MXKeyBackupData *keyBackupData = [keyBackupAlgorithm encryptGroupSession:session]; XCTAssertNotNil(keyBackupData); XCTAssertNotNil(keyBackupData.sessionData); // - Check [MXKeyBackupAlgorithm decryptKeyBackupData] returns stg - MXMegolmSessionData *sessionData = [aliceSession.crypto.backup.keyBackupAlgorithm decryptKeyBackupData:keyBackupData forSession:session.session.sessionIdentifier inRoom:roomId]; + MXMegolmSessionData *sessionData = [keyBackupAlgorithm decryptKeyBackupData:keyBackupData forSession:session.session.sessionIdentifier inRoom:roomId]; XCTAssertNotNil(sessionData); XCTAssertEqual(sessionData.isUntrusted, self.isUntrusted); diff --git a/changelog.d/pr-1578.change b/changelog.d/pr-1578.change new file mode 100644 index 0000000000..f467a04281 --- /dev/null +++ b/changelog.d/pr-1578.change @@ -0,0 +1 @@ +Crypto: Extract key backup engine From 65a6a87a983417c602712d0749e2f06b1b82bf6e Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Mon, 26 Sep 2022 15:48:51 +0300 Subject: [PATCH 09/30] Define event type prefix --- MatrixSDK/MXRestClient.h | 1 + MatrixSDK/MXRestClient.m | 1 + 2 files changed, 2 insertions(+) diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index 2f37ed01dd..bd252b7059 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -76,6 +76,7 @@ FOUNDATION_EXPORT NSString *const kMXAccountDataTypeIdentityServer; FOUNDATION_EXPORT NSString *const kMXAccountDataTypeAcceptedTerms; FOUNDATION_EXPORT NSString *const kMXAccountDataTypeBreadcrumbs; FOUNDATION_EXPORT NSString *const kMXAccountDataTypeAcceptedTermsKey; +FOUNDATION_EXPORT NSString *const kMXAccountDataTypeClientInformation; /** Account data keys diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index d558770185..3f236ff075 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -52,6 +52,7 @@ NSString *const kMXAccountDataTypeIdentityServer = @"m.identity_server"; NSString *const kMXAccountDataTypeAcceptedTerms = @"m.accepted_terms"; NSString *const kMXAccountDataTypeBreadcrumbs = @"im.vector.setting.breadcrumbs"; +NSString *const kMXAccountDataTypeClientInformation = @"io.element.matrix_client_information"; /** Account data keys From 70805a3c66302e89352cb82717f51e18d1d976bc Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Mon, 26 Sep 2022 15:49:10 +0300 Subject: [PATCH 10/30] Set the account data event if needed --- MatrixSDK/MXSession.m | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 0d447a5b08..d797ac7843 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -1137,6 +1137,8 @@ - (void)_resume:(void (^)(void))resumeDone // Relaunch live events stream (long polling) [self serverSyncWithServerTimeout:0 success:nil failure:nil clientTimeout:CLIENT_TIMEOUT_MS setPresence:self.preferredSyncPresenceString]; } + + [self refreshClientInformationIfNeeded]; } for (MXPeekingRoom *peekingRoom in peekingRooms) @@ -4550,6 +4552,34 @@ - (void)updateBreadcrumbsWithRoomWithId:(NSString *)roomId }]; } +- (void)refreshClientInformationIfNeeded +{ + NSString *bundleDisplayName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; + NSString *name = [NSString stringWithFormat:@"%@ iOS", bundleDisplayName]; + NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; + NSDictionary *updatedInfo = @{ + @"name": name, + @"version": version + }; + NSString *type = [NSString stringWithFormat:@"%@.%@", kMXAccountDataTypeClientInformation, self.myDeviceId]; + + NSDictionary *currentInfo = [self.accountData accountDataForEventType:type]; + + if ([updatedInfo isEqualToDictionary:currentInfo]) + { + MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: no need to update"); + } + else + { + // there is change, update the event + [self setAccountData:updatedInfo forType:type success:^{ + MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: updated successfully"); + } failure:^(NSError *error) { + MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: update failed: %@", error); + }]; + } +} + #pragma mark - Homeserver information - (MXWellKnown *)homeserverWellknown { From c6dd86294e6090140e6418c9eee77c52c27e7ef6 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Mon, 26 Sep 2022 15:51:43 +0300 Subject: [PATCH 11/30] Add changelog --- changelog.d/pr-1582.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-1582.change diff --git a/changelog.d/pr-1582.change b/changelog.d/pr-1582.change new file mode 100644 index 0000000000..c12bdf90fb --- /dev/null +++ b/changelog.d/pr-1582.change @@ -0,0 +1 @@ +MXSession: Set client information data if needed on resume. From 34c2d3585d660c385cd17aa550ebece2cd4cf57a Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 27 Sep 2022 11:21:51 +0300 Subject: [PATCH 12/30] Move `MXDevice` into its file and expose `lastSeenUserAgent` --- MatrixSDK.xcodeproj/project.pbxproj | 12 ++++++ MatrixSDK/JSONModels/MXDevice.h | 53 +++++++++++++++++++++++ MatrixSDK/JSONModels/MXDevice.m | 65 +++++++++++++++++++++++++++++ MatrixSDK/JSONModels/MXJSONModels.h | 28 ------------- MatrixSDK/JSONModels/MXJSONModels.m | 44 ------------------- MatrixSDK/MXRestClient.h | 1 + MatrixSDK/MatrixSDK.h | 2 +- 7 files changed, 132 insertions(+), 73 deletions(-) create mode 100644 MatrixSDK/JSONModels/MXDevice.h create mode 100644 MatrixSDK/JSONModels/MXDevice.m diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index d7de3a0b9f..d417c02492 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1642,6 +1642,10 @@ EC60EE09265CFFF400B39A4E /* MXGroupSyncProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = EC60EE05265CFFF400B39A4E /* MXGroupSyncProfile.m */; }; EC619D9224DD834B00663A80 /* MXPushGatewayRestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = EC619D9024DD834B00663A80 /* MXPushGatewayRestClient.m */; }; EC619D9324DD834B00663A80 /* MXPushGatewayRestClient.h in Headers */ = {isa = PBXBuildFile; fileRef = EC619D9124DD834B00663A80 /* MXPushGatewayRestClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6D007928E1F15400152144 /* MXDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = EC6D007728E1F15400152144 /* MXDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6D007A28E1F15400152144 /* MXDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = EC6D007728E1F15400152144 /* MXDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EC6D007B28E1F15400152144 /* MXDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = EC6D007828E1F15400152144 /* MXDevice.m */; }; + EC6D007C28E1F15400152144 /* MXDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = EC6D007828E1F15400152144 /* MXDevice.m */; }; EC746C56274E5197002AD24C /* MXThreadingServiceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC746C55274E5197002AD24C /* MXThreadingServiceUnitTests.swift */; }; EC746C57274E5197002AD24C /* MXThreadingServiceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC746C55274E5197002AD24C /* MXThreadingServiceUnitTests.swift */; }; EC746C59274E61EF002AD24C /* MXThreadingServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC746C58274E61EF002AD24C /* MXThreadingServiceTests.swift */; }; @@ -2822,6 +2826,8 @@ EC60EE05265CFFF400B39A4E /* MXGroupSyncProfile.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXGroupSyncProfile.m; sourceTree = ""; }; EC619D9024DD834B00663A80 /* MXPushGatewayRestClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXPushGatewayRestClient.m; sourceTree = ""; }; EC619D9124DD834B00663A80 /* MXPushGatewayRestClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXPushGatewayRestClient.h; sourceTree = ""; }; + EC6D007728E1F15400152144 /* MXDevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXDevice.h; sourceTree = ""; }; + EC6D007828E1F15400152144 /* MXDevice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXDevice.m; sourceTree = ""; }; EC746C55274E5197002AD24C /* MXThreadingServiceUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXThreadingServiceUnitTests.swift; sourceTree = ""; }; EC746C58274E61EF002AD24C /* MXThreadingServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXThreadingServiceTests.swift; sourceTree = ""; }; EC8A536C25B1BC77004E0802 /* MXCallCandidate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCallCandidate.h; sourceTree = ""; }; @@ -3806,6 +3812,8 @@ 8EC51109256822B400EC4E5B /* MXTaggedEvents.m */, EC05473225FF8A3C0047ECD7 /* MXVirtualRoomInfo.h */, EC05473325FF8A3C0047ECD7 /* MXVirtualRoomInfo.m */, + EC6D007728E1F15400152144 /* MXDevice.h */, + EC6D007828E1F15400152144 /* MXDevice.m */, ); path = JSONModels; sourceTree = ""; @@ -5549,6 +5557,7 @@ F08B8D5C1E014711006171A8 /* NSData+MatrixSDK.h in Headers */, C60165381E3AA57900B92CFA /* MXSDKOptions.h in Headers */, 32CEEF4F23B0AB030039BA98 /* MXCrossSigning.h in Headers */, + EC6D007928E1F15400152144 /* MXDevice.h in Headers */, EC619D9324DD834B00663A80 /* MXPushGatewayRestClient.h in Headers */, 32C78B68256CFC4D008130B1 /* MXCryptoVersion.h in Headers */, 320B3934239FA56900BE2C06 /* MXKeyVerificationByDMRequest.h in Headers */, @@ -5864,6 +5873,7 @@ 1838928927031D1D003F0C4F /* MXSendReplyEventStringLocalizerProtocol.h in Headers */, EC60EDDB265CFF0600B39A4E /* MXInvitedRoomSync.h in Headers */, B14EF2C82397E90400758AF0 /* MXCallManager.h in Headers */, + EC6D007A28E1F15400152144 /* MXDevice.h in Headers */, EC40384B289A7F260067D5B8 /* MXAes256KeyBackupAlgorithm.h in Headers */, B14EF2C92397E90400758AF0 /* MXRealmReactionRelation.h in Headers */, 91F0685E2767CA430079F8FA /* MXTaskProfileName.h in Headers */, @@ -6799,6 +6809,7 @@ 321CFDF92254E721004D31DF /* MXTransactionCancelCode.m in Sources */, B19A30C22404268600FB6F35 /* MXVerifyingAnotherUserQRCodeData.m in Sources */, EC8A53BD25B1BC77004E0802 /* MXCallRejectEventContent.m in Sources */, + EC6D007B28E1F15400152144 /* MXDevice.m in Sources */, ECCA02BE27348FE300B6F34F /* MXThread.swift in Sources */, ED7019E52886C32900FC31B9 /* MXSASTransactionV2.swift in Sources */, EC1848C72686174E00865E16 /* MXiOSAudioOutputRouteType.swift in Sources */, @@ -7398,6 +7409,7 @@ 3229535425A5F7220012FCF0 /* MXBackgroundCryptoStore.m in Sources */, B14EF2812397E90400758AF0 /* MXNotificationCenter.m in Sources */, EC60ED60265CFC2C00B39A4E /* MXSyncResponse.m in Sources */, + EC6D007C28E1F15400152144 /* MXDevice.m in Sources */, 3A108AB825812995005EEBE9 /* MXKeyProvider.m in Sources */, B14EF2822397E90400758AF0 /* MXDeviceList.m in Sources */, ED7019E62886C32900FC31B9 /* MXSASTransactionV2.swift in Sources */, diff --git a/MatrixSDK/JSONModels/MXDevice.h b/MatrixSDK/JSONModels/MXDevice.h new file mode 100644 index 0000000000..80c024ab05 --- /dev/null +++ b/MatrixSDK/JSONModels/MXDevice.h @@ -0,0 +1,53 @@ +// +// 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + `MXDevice` represents a device of the current user. + */ +@interface MXDevice : MXJSONModel + +/** + A unique identifier of the device. + */ +@property (nonatomic) NSString *deviceId; + +/** + The display name set by the user for this device. Absent if no name has been set. + */ +@property (nonatomic, nullable) NSString *displayName; + +/** + The IP address where this device was last seen. (May be a few minutes out of date, for efficiency reasons). + */ +@property (nonatomic, nullable) NSString *lastSeenIp; + +/** + The timestamp (in milliseconds since the unix epoch) when this devices was last seen. (May be a few minutes out of date, for efficiency reasons). + */ +@property (nonatomic) uint64_t lastSeenTs; + +/** + The latest recorded usr agent for the device. + */ +@property (nonatomic, nullable) NSString *lastSeenUserAgent; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/JSONModels/MXDevice.m b/MatrixSDK/JSONModels/MXDevice.m new file mode 100644 index 0000000000..958bc6e3db --- /dev/null +++ b/MatrixSDK/JSONModels/MXDevice.m @@ -0,0 +1,65 @@ +// +// 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 "MXDevice.h" + +NSString* const kDeviceIdJSONKey = @"device_id"; +NSString* const kDisplayNameJSONKey = @"display_name"; +NSString* const kLastSeenIPJSONKey = @"last_seen_ip"; +NSString* const kLastSeenTimestampJSONKey = @"last_seen_ts"; +NSString* const kLastSeenUserAgentJSONKey = @"org.matrix.msc3852.last_seen_user_agent"; + +@implementation MXDevice + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXDevice *device = [[MXDevice alloc] init]; + if (device) + { + MXJSONModelSetString(device.deviceId, JSONDictionary[kDeviceIdJSONKey]); + MXJSONModelSetString(device.displayName, JSONDictionary[kDisplayNameJSONKey]); + MXJSONModelSetString(device.lastSeenIp, JSONDictionary[kLastSeenIPJSONKey]); + MXJSONModelSetUInt64(device.lastSeenTs, JSONDictionary[kLastSeenTimestampJSONKey]); + MXJSONModelSetString(device.lastSeenUserAgent, JSONDictionary[kLastSeenUserAgentJSONKey]); + } + + return device; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder +{ + self = [super init]; + if (self) + { + _deviceId = [aDecoder decodeObjectForKey:kDeviceIdJSONKey]; + _displayName = [aDecoder decodeObjectForKey:kDisplayNameJSONKey]; + _lastSeenIp = [aDecoder decodeObjectForKey:kLastSeenIPJSONKey]; + _lastSeenTs = [((NSNumber*)[aDecoder decodeObjectForKey:kLastSeenTimestampJSONKey]) unsignedLongLongValue]; + _lastSeenUserAgent = [aDecoder decodeObjectForKey:kLastSeenUserAgentJSONKey]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder +{ + [aCoder encodeObject:_deviceId forKey:kDeviceIdJSONKey]; + [aCoder encodeObject:_displayName forKey:kDisplayNameJSONKey]; + [aCoder encodeObject:_lastSeenIp forKey:kLastSeenIPJSONKey]; + [aCoder encodeObject:@(_lastSeenTs) forKey:kLastSeenTimestampJSONKey]; + [aCoder encodeObject:_lastSeenUserAgent forKey:kLastSeenUserAgentJSONKey]; +} + +@end diff --git a/MatrixSDK/JSONModels/MXJSONModels.h b/MatrixSDK/JSONModels/MXJSONModels.h index e79bc194ee..4f63d9498f 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.h +++ b/MatrixSDK/JSONModels/MXJSONModels.h @@ -1143,34 +1143,6 @@ FOUNDATION_EXPORT NSString *const kMXPushRuleScopeStringDevice; @end -#pragma mark - Device Management -/** - `MXDevice` represents a device of the current user. - */ -@interface MXDevice : MXJSONModel - - /** - A unique identifier of the device. - */ - @property (nonatomic) NSString *deviceId; - - /** - The display name set by the user for this device. Absent if no name has been set. - */ - @property (nonatomic) NSString *displayName; - - /** - The IP address where this device was last seen. (May be a few minutes out of date, for efficiency reasons). - */ - @property (nonatomic) NSString *lastSeenIp; - - /** - The timestamp (in milliseconds since the unix epoch) when this devices was last seen. (May be a few minutes out of date, for efficiency reasons). - */ - @property (nonatomic) uint64_t lastSeenTs; - -@end - #pragma mark - Groups (Communities) /** diff --git a/MatrixSDK/JSONModels/MXJSONModels.m b/MatrixSDK/JSONModels/MXJSONModels.m index 639ff6b0d2..dd8ed92380 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.m +++ b/MatrixSDK/JSONModels/MXJSONModels.m @@ -1259,50 +1259,6 @@ - (NSDictionary *)JSONDictionary @end -#pragma mark - Device Management - -@implementation MXDevice - -+ (id)modelFromJSON:(NSDictionary *)JSONDictionary -{ - MXDevice *device = [[MXDevice alloc] init]; - if (device) - { - MXJSONModelSetString(device.deviceId, JSONDictionary[@"device_id"]); - MXJSONModelSetString(device.displayName, JSONDictionary[@"display_name"]); - MXJSONModelSetString(device.lastSeenIp, JSONDictionary[@"last_seen_ip"]); - MXJSONModelSetUInt64(device.lastSeenTs, JSONDictionary[@"last_seen_ts"]); - } - - return device; -} - -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - self = [super init]; - if (self) - { - _deviceId = [aDecoder decodeObjectForKey:@"device_id"]; - _displayName = [aDecoder decodeObjectForKey:@"display_name"]; - _lastSeenIp = [aDecoder decodeObjectForKey:@"last_seen_ip"]; - _lastSeenTs = [((NSNumber*)[aDecoder decodeObjectForKey:@"last_seen_ts"]) unsignedLongLongValue]; - } - return self; -} - -- (void)encodeWithCoder:(NSCoder *)aCoder -{ - [aCoder encodeObject:_deviceId forKey:@"device_id"]; - if (_displayName) - { - [aCoder encodeObject:_displayName forKey:@"display_name"]; - } - [aCoder encodeObject:_lastSeenIp forKey:@"last_seen_ip"]; - [aCoder encodeObject:@(_lastSeenTs) forKey:@"last_seen_ts"]; -} - -@end - #pragma mark - Groups (Communities) @implementation MXGroupProfile diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index bd252b7059..4abb4ff1b0 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -52,6 +52,7 @@ @class MXDeviceListResponse; @class MXSpaceChildrenRequestParameters; @class MXCapabilities; +@class MXDevice; #pragma mark - Constants definitions /** diff --git a/MatrixSDK/MatrixSDK.h b/MatrixSDK/MatrixSDK.h index c39fa05d3a..202eaca6d8 100644 --- a/MatrixSDK/MatrixSDK.h +++ b/MatrixSDK/MatrixSDK.h @@ -194,4 +194,4 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXBeaconInfo.h" #import "MXBeacon.h" #import "MXEventAssetType.h" - +#import "MXDevice.h" From de977129e56c1f0a590172cf5b270063fe4c0069 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Tue, 27 Sep 2022 11:25:21 +0300 Subject: [PATCH 13/30] Add changelog --- changelog.d/pr-1583.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-1583.change diff --git a/changelog.d/pr-1583.change b/changelog.d/pr-1583.change new file mode 100644 index 0000000000..3cf3bd72da --- /dev/null +++ b/changelog.d/pr-1583.change @@ -0,0 +1 @@ +MXDevice: Move to dedicated file and implement MSC-3852. From d15204e9810b21d7ef99140cad9708705462239d Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Tue, 27 Sep 2022 11:55:05 +0100 Subject: [PATCH 14/30] Simplify Key Backup Engine API --- MatrixSDK.xcodeproj/project.pbxproj | 18 +- .../Crypto/Data/Store/MXCryptoSecretStore.h | 52 ++++ MatrixSDK/Crypto/Data/Store/MXCryptoStore.h | 31 +-- .../KeyBackup/Engine/MXKeyBackupEngine.h | 76 ++---- .../KeyBackup/Engine/MXKeyBackupPayload.swift | 30 --- .../Engine/MXNativeKeyBackupEngine.m | 239 ++++++++++-------- MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h | 8 - MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m | 152 ++++------- .../Crypto/KeyBackup/MXKeyBackup_Private.h | 2 +- MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h | 5 + MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m | 8 + MatrixSDK/Crypto/MXCrypto.m | 12 +- MatrixSDK/Crypto/Recovery/MXRecoveryService.h | 10 + MatrixSDK/Crypto/Recovery/MXRecoveryService.m | 115 ++++----- .../MXRecoveryServiceDependencies.swift | 43 ++++ .../Recovery/MXRecoveryService_Private.h | 7 - MatrixSDKTests/MXBaseKeyBackupTests.m | 12 +- 17 files changed, 416 insertions(+), 404 deletions(-) create mode 100644 MatrixSDK/Crypto/Data/Store/MXCryptoSecretStore.h delete mode 100644 MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift create mode 100644 MatrixSDK/Crypto/Recovery/MXRecoveryServiceDependencies.swift diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index f2666c47f9..98ad5440c7 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1883,6 +1883,10 @@ ED8F1D352885B07500F897E7 /* MXCrossSigningInfoUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D1628857FE600F897E7 /* MXCrossSigningInfoUnitTests.swift */; }; ED8F1D3B2885BB2D00F897E7 /* MXCryptoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */; }; ED8F1D3C2885BB2D00F897E7 /* MXCryptoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */; }; + EDAAC41C28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */ = {isa = PBXBuildFile; fileRef = EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDAAC41D28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */ = {isa = PBXBuildFile; fileRef = EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDAAC41F28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */; }; + EDAAC42028E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */; }; EDB4209227DF77390036AF39 /* MXEventsEnumeratorOnArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209027DF77310036AF39 /* MXEventsEnumeratorOnArrayTests.swift */; }; EDB4209327DF77390036AF39 /* MXEventsEnumeratorOnArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209027DF77310036AF39 /* MXEventsEnumeratorOnArrayTests.swift */; }; EDB4209527DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209427DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift */; }; @@ -1916,8 +1920,6 @@ EDE70DC528DA1B7F00099736 /* MXCryptoTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 3250E7C8220C913900736CB5 /* MXCryptoTools.h */; settings = {ATTRIBUTES = (Public, ); }; }; EDE70DC828DA22F800099736 /* MXKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */; settings = {ATTRIBUTES = (Public, ); }; }; EDE70DC928DA22F800099736 /* MXKeyBackupEngine.h in Headers */ = {isa = PBXBuildFile; fileRef = EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EDEC2E5E28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */; }; - EDEC2E5F28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */; }; EDF1B6902876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */; }; EDF1B6912876CD2C00BBBCEE /* MXTaskQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */; }; EDF1B6932876CD8600BBBCEE /* MXTaskQueueUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDF1B6922876CD8600BBBCEE /* MXTaskQueueUnitTests.swift */; }; @@ -2962,6 +2964,8 @@ ED8F1D312885AC5700F897E7 /* Device+Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Device+Stub.swift"; sourceTree = ""; }; ED8F1D332885ADE200F897E7 /* MXCryptoProtocolStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoProtocolStubs.swift; sourceTree = ""; }; ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoProtocols.swift; sourceTree = ""; }; + EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCryptoSecretStore.h; sourceTree = ""; }; + EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRecoveryServiceDependencies.swift; sourceTree = ""; }; 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 = ""; }; @@ -2978,7 +2982,6 @@ EDD578EB2881C38C006739DD /* MXCrossSigningV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCrossSigningV2.swift; sourceTree = ""; }; EDE1B13A28B7BEAB000DEEE8 /* MXCrossSigningV2UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCrossSigningV2UnitTests.swift; sourceTree = ""; }; EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXKeyBackupEngine.h; sourceTree = ""; }; - EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXKeyBackupPayload.swift; sourceTree = ""; }; EDF1B68F2876CD2C00BBBCEE /* MXTaskQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXTaskQueue.swift; sourceTree = ""; }; EDF1B6922876CD8600BBBCEE /* MXTaskQueueUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXTaskQueueUnitTests.swift; sourceTree = ""; }; EDF4678627E3331D00435913 /* EventsEnumeratorDataSourceStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsEnumeratorDataSourceStub.swift; sourceTree = ""; }; @@ -3826,6 +3829,7 @@ 3284A59C1DB7C00600A09972 /* Store */ = { isa = PBXGroup; children = ( + EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */, 3284A59D1DB7C00600A09972 /* MXCryptoStore.h */, 3259CD4C1DF85F6D00186944 /* MXRealmCryptoStore */, ); @@ -4317,6 +4321,7 @@ isa = PBXGroup; children = ( 325AF3DE24897D2300EF937D /* Data */, + EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */, 32F00AB92488E1CD00131741 /* MXRecoveryService.h */, 32F00ABA2488E1CD00131741 /* MXRecoveryService.m */, 32F00ABF2488FB3600131741 /* MXRecoveryService_Private.h */, @@ -5367,7 +5372,6 @@ EDE70DC628DA22E200099736 /* Engine */ = { isa = PBXGroup; children = ( - EDEC2E5D28DCADE400982DD3 /* MXKeyBackupPayload.swift */, EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */, EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */, EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */, @@ -5468,6 +5472,7 @@ B146D47421A5945800D8C2C6 /* MXAntivirusScanStatus.h in Headers */, 322691361E5EFF8700966A6E /* MXDeviceListOperationsPool.h in Headers */, 3281E8B719E42DFE00976E1A /* MXJSONModel.h in Headers */, + EDAAC41C28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */, B135066127E9CB6400BD3276 /* MXBeaconInfo.h in Headers */, EC5C562827A36EDB0014CBE9 /* MXInReplyTo.h in Headers */, EC8A539325B1BC77004E0802 /* MXCallSessionDescription.h in Headers */, @@ -6084,6 +6089,7 @@ EC11658E270F3ABF0089FA56 /* RLMRealm+MatrixSDK.h in Headers */, 324AAC7E2399143400380A66 /* MXKeyVerificationCancel.h in Headers */, B14EF3372397E90400758AF0 /* MXRoomTombStoneContent.h in Headers */, + EDAAC41D28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */, 3274538B23FD918800438328 /* MXKeyVerificationByToDeviceRequest.h in Headers */, B14EF3382397E90400758AF0 /* MXFilterObject.h in Headers */, B14EF3392397E90400758AF0 /* MXRealmReactionCount.h in Headers */, @@ -6507,7 +6513,6 @@ 32C235731F827F3800E38FC5 /* MXRoomOperation.m in Sources */, 322A51C41D9AC8FE00C8536D /* MXCryptoAlgorithms.m in Sources */, B1798D0824091A0100308A8F /* MXBase64Tools.m in Sources */, - EDEC2E5E28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */, B182B08823167A640057972E /* MXIdentityServerHashDetails.m in Sources */, EC60EDBE265CFE8600B39A4E /* MXRoomSyncAccountData.m in Sources */, EC1165BE27107E330089FA56 /* MXSuggestedRoomListDataFetcher.swift in Sources */, @@ -6810,6 +6815,7 @@ 329FB1801A0B665800A5E88E /* MXUser.m in Sources */, 324AAC73239913AD00380A66 /* MXKeyVerificationDone.m in Sources */, B11556EE230C45C600B2A2CF /* MXIdentityServerRestClient.swift in Sources */, + EDAAC41F28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */, 321CFDE722525A49004D31DF /* MXSASTransaction.m in Sources */, 32720D9D222EAA6F0086FFF5 /* MXDiscoveredClientConfig.m in Sources */, EC5C560C2798CEA00014CBE9 /* NSDictionary+MutableDeepCopy.m in Sources */, @@ -7108,7 +7114,6 @@ B14EF1F32397E90400758AF0 /* MXServerNotices.m in Sources */, B14EF1F42397E90400758AF0 /* MXMemoryStore.m in Sources */, B14EF1F52397E90400758AF0 /* MXAggregationPaginatedResponse.m in Sources */, - EDEC2E5F28DCADE400982DD3 /* MXKeyBackupPayload.swift in Sources */, B14EF1F62397E90400758AF0 /* MXEventReplace.m in Sources */, B16F35A325F916A00029AE98 /* MXRoomTypeMapper.swift in Sources */, EC1165BF27107E330089FA56 /* MXSuggestedRoomListDataFetcher.swift in Sources */, @@ -7411,6 +7416,7 @@ B14EF2752397E90400758AF0 /* MXResponse.swift in Sources */, B14EF2772397E90400758AF0 /* MXDecryptionResult.m in Sources */, B14EF2782397E90400758AF0 /* MXTransactionCancelCode.m in Sources */, + EDAAC42028E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */, B14EF2792397E90400758AF0 /* MXEventListener.m in Sources */, B1710B202613D01400A9B429 /* MXSpaceChildrenRequestParameters.swift in Sources */, B14EF27A2397E90400758AF0 /* MXSessionEventListener.swift in Sources */, diff --git a/MatrixSDK/Crypto/Data/Store/MXCryptoSecretStore.h b/MatrixSDK/Crypto/Data/Store/MXCryptoSecretStore.h new file mode 100644 index 0000000000..b51f3e5ac7 --- /dev/null +++ b/MatrixSDK/Crypto/Data/Store/MXCryptoSecretStore.h @@ -0,0 +1,52 @@ +// +// 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 MXCryptoSecretStore_h +#define MXCryptoSecretStore_h + +/** + The `MXCryptoSecretStore` protocol defines an interface that must be implemented in order to store + local secrets in the context of SSSS. + */ +@protocol MXCryptoSecretStore + +/** + Store a secret. + + @param secret the secret. + @param secretId the id of the secret. + */ +- (void)storeSecret:(NSString*)secret withSecretId:(NSString*)secretId; + +/** + Retrieve a secret. + + @param secretId the id of the secret. + @return the secret. Nil if the secret does not exist. + */ +- (NSString*)secretWithSecretId:(NSString*)secretId; + + +/** + Delete a secret. + + @param secretId the id of the secret. + */ +- (void)deleteSecretWithSecretId:(NSString*)secretId; + +@end + +#endif /* MXCryptoSecretStore_h */ diff --git a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h index c78eaeae71..be1886ec4b 100644 --- a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h +++ b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h @@ -31,6 +31,7 @@ #import "MXCrossSigningInfo.h" #import "MXOutgoingRoomKeyRequest.h" #import "MXIncomingRoomKeyRequest.h" +#import "MXCryptoSecretStore.h" @class OLMAccount; @class OLMOutboundGroupSession; @@ -39,7 +40,7 @@ The `MXCryptoStore` protocol defines an interface that must be implemented in order to store crypto data for a matrix account. */ -@protocol MXCryptoStore +@protocol MXCryptoStore /** Indicate if the store contains data for the passed account. @@ -502,34 +503,6 @@ */ - (MXUsersDevicesMap *> *)incomingRoomKeyRequests; - -#pragma mark - Secret storage - -/** - Store a secret. - - @param secret the secret. - @param secretId the id of the secret. - */ -- (void)storeSecret:(NSString*)secret withSecretId:(NSString*)secretId; - -/** - Retrieve a secret. - - @param secretId the id of the secret. - @return the secret. Nil if the secret does not exist. - */ -- (NSString*)secretWithSecretId:(NSString*)secretId; - - -/** - Delete a secret. - - @param secretId the id of the secret. - */ -- (void)deleteSecretWithSecretId:(NSString*)secretId; - - #pragma mark - Crypto settings /** diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h b/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h index 227fa8a69f..5bd72d3398 100644 --- a/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h +++ b/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupEngine.h @@ -21,6 +21,7 @@ #import "MXMegolmBackupCreationInfo.h" #import "MXKeyBackupVersionTrust.h" #import "MXKeyBackupAlgorithm.h" +#import "MXKeyBackupData.h" NS_ASSUME_NONNULL_BEGIN @@ -34,20 +35,20 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Enable / Disable engine /** - Is the engine enabled to backup room keys + Is the engine enabled to backup keys */ @property (nonatomic, readonly) BOOL enabled; /** Current version of the backup */ -@property (nonatomic, readonly) NSString *version; +@property (nullable, nonatomic, readonly) NSString *version; /** Enable a new backup version that will replace any previous version */ -- (BOOL)enableBackupWithVersion:(MXKeyBackupVersion *)version - error:(NSError **)error; +- (BOOL)enableBackupWithKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error; /** Disable the current backup and reset any backup-related state */ @@ -63,39 +64,25 @@ NS_ASSUME_NONNULL_BEGIN /** Save a new private key */ -- (void)savePrivateKey:(NSString *)privateKey; +- (void)savePrivateKey:(NSData *)privateKey version:(NSString *)version; /** - Save a new private key using a recovery key + Check to see if the store contains a valid private key */ -- (void)saveRecoveryKey:(NSString *)recoveryKey; +- (BOOL)hasValidPrivateKey; /** - Delete the currently stored private key + Check to see if the store contains a valid private key that matches a specific backup version */ -- (void)deletePrivateKey; +- (BOOL)hasValidPrivateKeyForKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion; /** - Check if a private key matches current key backup version + Create valid private key from a recovery key for a specific backup version */ -- (BOOL)isValidPrivateKey:(NSData *)privateKey - error:(NSError **)error; +- (nullable NSData *)validPrivateKeyForRecoveryKey:(NSString *)recoveryKey + forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error; -/** - Check if a private key matches key backup version - */ -- (BOOL)isValidPrivateKey:(NSData *)privateKey - forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion - error:(NSError **)error; - -/** - Check if a recovery key matches key backup authentication data - */ -- (BOOL)isValidRecoveryKey:(NSString *)recoveryKey - forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion - error:(NSError **)error; - -- (void)validateKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion; /** Compute the recovery key from a password and key backup auth data @@ -109,21 +96,21 @@ NS_ASSUME_NONNULL_BEGIN /** Prepare a new backup version to be uploaded to the server */ -- (void)prepareKeyBackupVersionWithPassword:(NSString *)password - algorithm:(NSString *)algorithm +- (void)prepareKeyBackupVersionWithPassword:(nullable NSString *)password + algorithm:(nullable NSString *)algorithm success:(void (^)(MXMegolmBackupCreationInfo *))success failure:(void (^)(NSError *))failure; /** Get the current trust level of the backup version */ -- (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBackupVersion *)keyBackupVersion; +- (MXKeyBackupVersionTrust *)trustForKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion; /** Extract authentication data from a backup */ -- (nullable id)megolmBackupAuthDataFromKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion - error:(NSError **)error; +- (nullable id)authDataFromKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error; /** Sign an object with backup signing key @@ -143,26 +130,19 @@ NS_ASSUME_NONNULL_BEGIN - (NSProgress *)backupProgress; /** - Payload of room keys to be backed up to the server - */ -- (nullable MXKeyBackupPayload *)roomKeysBackupPayload; - -/** - Decrypt backup data using private key + Backup keys to the server */ -- (nullable MXMegolmSessionData *)decryptKeyBackupData:(MXKeyBackupData *)keyBackupData - keyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion - privateKey:(NSData *)privateKey - forSession:(NSString *)sessionId - inRoom:(NSString *)roomId; +- (void)backupKeysWithSuccess:(void (^)(void))success + failure:(void (^)(NSError *error))failure; /** - Import decrypted room keys + Import encypted backup keys */ -- (void)importMegolmSessionDatas:(NSArray*)keys - backUp:(BOOL)backUp - success:(void (^)(NSUInteger total, NSUInteger imported))success - failure:(void (^)(NSError *error))failure; +- (void)importKeysWithKeysBackupData:(MXKeysBackupData *)keysBackupData + privateKey:(NSData*)privateKey + keyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + success:(void (^)(NSUInteger totalKeys, NSUInteger importedKeys))success + failure:(void (^)(NSError *error))failure; @end diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift b/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift deleted file mode 100644 index 12f228efd6..0000000000 --- a/MatrixSDK/Crypto/KeyBackup/Engine/MXKeyBackupPayload.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// 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 - -/// Payload of room keys to backup and a completion block to call after -/// successful backup. -@objcMembers -public class MXKeyBackupPayload: NSObject { - public let backupData: MXKeysBackupData - public let completion: ([AnyHashable: Any]) -> Void - - @objc public init(backupData: MXKeysBackupData, completion: @escaping ([AnyHashable: Any]) -> Void) { - self.backupData = backupData - self.completion = completion - } -} diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m b/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m index 19983bd7f9..8fc278ba15 100644 --- a/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m +++ b/MatrixSDK/Crypto/KeyBackup/Engine/MXNativeKeyBackupEngine.m @@ -23,6 +23,7 @@ #import "OLMInboundGroupSession.h" #import "MatrixSDKSwiftHeader.h" #import "MXRecoveryKey.h" +#import "MXKeyBackupData.h" /** Maximum number of keys to send at a time to the homeserver. @@ -82,17 +83,19 @@ - (NSString *)version return self.crypto.store.backupVersion; } -- (BOOL)enableBackupWithVersion:(MXKeyBackupVersion *)version error:(NSError **)error +- (BOOL)enableBackupWithKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion error:(NSError **)error { - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:version error:error]; + [self validateKeyBackupVersion:keyBackupVersion]; + + id authData = [self authDataFromKeyBackupVersion:keyBackupVersion error:error]; if (!authData) { return NO; } - self.keyBackupVersion = version; - self.crypto.store.backupVersion = version.version; - Class algorithmClass = AlgorithmClassesByName[version.algorithm]; + self.keyBackupVersion = keyBackupVersion; + self.crypto.store.backupVersion = keyBackupVersion.version; + Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; // store the desired backup algorithm self.keyBackupAlgorithm = [[algorithmClass alloc] initWithCrypto:self.crypto authData:authData keyGetterBlock:^NSData * _Nullable{ return self.privateKey; @@ -114,7 +117,7 @@ - (void)disableBackup #pragma mark - Private / Recovery key management -- (nullable NSData*)privateKey +- (nullable NSData *)privateKey { NSString *privateKeyBase64 = [self.crypto.store secretWithSecretId:MXSecretId.keyBackup]; if (!privateKeyBase64) @@ -126,47 +129,55 @@ - (nullable NSData*)privateKey return [MXBase64Tools dataFromBase64:privateKeyBase64]; } -- (void)savePrivateKey:(NSString *)privateKey +- (void)savePrivateKey:(NSData *)privateKey version:(NSString *)version { - [self.crypto.store storeSecret:privateKey withSecretId:MXSecretId.keyBackup]; + NSString *privateKeyBase64 = [MXBase64Tools unpaddedBase64FromData:privateKey]; + [self.crypto.store storeSecret:privateKeyBase64 withSecretId:MXSecretId.keyBackup]; } -- (void)saveRecoveryKey:(NSString *)recoveryKey +- (BOOL)hasValidPrivateKey { - NSError *error; - OLMPkDecryption *decryption = [self pkDecryptionFromRecoveryKey:recoveryKey error:&error]; - if (!decryption) + NSData *privateKey = self.privateKey; + if (!privateKey) { - MXLogDebug(@"[MXNativeKeyBackupEngine] saveRecoveryKey: Cannot create OLMPkDecryption. Error: %@", error); - return; + MXLogDebug(@"[MXNativeKeyBackupEngine] hasValidPrivateKey: No private key"); + return NO; } - NSString *privateKeyBase64 = [MXBase64Tools unpaddedBase64FromData:decryption.privateKey]; - [self savePrivateKey:privateKeyBase64]; -} - -- (void)deletePrivateKey -{ - [self.crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; -} - -- (BOOL)isValidPrivateKey:(NSData *)privateKey - error:(NSError **)error -{ - return [self.keyBackupAlgorithm keyMatches:privateKey error:error]; + NSError *error; + BOOL keyMatches = [self.keyBackupAlgorithm keyMatches:privateKey error:&error]; + if (!keyMatches) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] hasValidPrivateKey: Error: Private key does not match: %@", error); + [self.crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; + return NO; + } + return YES; } -- (BOOL)isValidPrivateKey:(NSData *)privateKey - forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion - error:(NSError **)error +- (BOOL)hasValidPrivateKeyForKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion { + NSData *privateKey = self.privateKey; + if (!privateKey) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] hasValidPrivateKeyForKeyBackupVersion: No private key"); + return NO; + } + + NSError *error; id algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey]; - return [algorithm keyMatches:privateKey error:error]; + BOOL keyMatches = [algorithm keyMatches:privateKey error:&error]; + if (!keyMatches) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] hasValidPrivateKeyForKeyBackupVersion: Error: Private key does not match: %@", error); + return NO; + } + return YES; } -- (BOOL)isValidRecoveryKey:(NSString*)recoveryKey - forKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion - error:(NSError **)error +- (NSData *)validPrivateKeyForRecoveryKey:(NSString *)recoveryKey + forKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error { NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:error]; @@ -180,7 +191,7 @@ - (BOOL)isValidRecoveryKey:(NSString*)recoveryKey userInfo:@{ NSLocalizedDescriptionKey: @"Invalid recovery key or password" }]; - return NO; + return nil; } Class algorithm = AlgorithmClassesByName[keyBackupVersion.algorithm]; @@ -193,7 +204,7 @@ - (BOOL)isValidRecoveryKey:(NSString*)recoveryKey userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Unknown algorithm (%@)", keyBackupVersion.algorithm] }]; - return NO; + return nil; } BOOL result = [algorithm keyMatches:privateKey withAuthData:keyBackupVersion.authData error:error]; @@ -208,44 +219,15 @@ - (BOOL)isValidRecoveryKey:(NSString*)recoveryKey }]; } - return result; -} - -- (void)validateKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion -{ - // Check private keys - if (self.privateKey) - { - Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; - if (algorithmClass == NULL) - { - NSString *message = [NSString stringWithFormat:@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: unknown algorithm: %@", keyBackupVersion.algorithm]; - MXLogError(message); - return; - } - if (![algorithmClass checkBackupVersion:keyBackupVersion]) - { - MXLogError(@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: invalid backup data returned"); - return; - } - - NSData *privateKey = self.privateKey; - NSError *error; - BOOL keyMatches = [algorithmClass keyMatches:privateKey withAuthData:keyBackupVersion.authData error:&error]; - if (error || !keyMatches) - { - MXLogDebug(@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: -> private key does not match: %@, will be removed", error); - [self.crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; - } - } + return privateKey; } -- (nullable NSString*)recoveryKeyFromPassword:(NSString*)password - inKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion +- (nullable NSString*)recoveryKeyFromPassword:(NSString *)password + inKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion error:(NSError **)error { // Extract MXBaseKeyBackupAuthData - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:error]; + id authData = [self authDataFromKeyBackupVersion:keyBackupVersion error:error]; if (*error) { return nil; @@ -360,14 +342,14 @@ - (void)prepareKeyBackupVersionWithPassword:(NSString *)password }]; } -- (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBackupVersion *)keyBackupVersion +- (MXKeyBackupVersionTrust *)trustForKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion { NSString *myUserId = self.crypto.matrixRestClient.credentials.userId; MXKeyBackupVersionTrust *keyBackupVersionTrust = [MXKeyBackupVersionTrust new]; NSError *error; - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; + id authData = [self authDataFromKeyBackupVersion:keyBackupVersion error:&error]; if (error) { MXLogDebug(@"[MXNativeKeyBackupEngine] trustForKeyBackupVersion: Key backup is absent or missing required data"); @@ -452,7 +434,8 @@ - (MXKeyBackupVersionTrust *)trustForKeyBackupVersionFromCryptoQueue:(MXKeyBacku return keyBackupVersionTrust; } -- (nullable id)megolmBackupAuthDataFromKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion error:(NSError**)error +- (nullable id)authDataFromKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + error:(NSError **)error { Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; if (algorithmClass == NULL) @@ -494,12 +477,17 @@ - (NSProgress *)backupProgress return progress; } -- (MXKeyBackupPayload *)roomKeysBackupPayload +- (void)backupKeysWithSuccess:(void (^)(void))success + failure:(void (^)(NSError *))failure { if (!self.keyBackupAlgorithm) { MXLogDebug(@"[MXNativeKeyBackupEngine] roomKeysBackupPayload: No known backup algorithm"); - return nil; + NSError *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorUnknownAlgorithm + userInfo:nil]; + failure(error); + return; } // Get a chunk of keys to backup @@ -546,35 +534,65 @@ - (MXKeyBackupPayload *)roomKeysBackupPayload MXKeysBackupData *keysBackupData = [MXKeysBackupData new]; keysBackupData.rooms = rooms; + // Make the request MXWeakify(self); - return [[MXKeyBackupPayload alloc] initWithBackupData:keysBackupData - completion:^(NSDictionary *JSONResponse) { + [self.crypto.matrixRestClient sendKeysBackup:keysBackupData version:self.keyBackupVersion.version success:^(NSDictionary *JSONResponse){ MXStrongifyAndReturnIfNil(self); [self.crypto.store markBackupDoneForInboundGroupSessions:sessions]; - }]; + success(); + } failure:failure]; } -- (MXMegolmSessionData *)decryptKeyBackupData:(MXKeyBackupData *)keyBackupData - keyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion - privateKey:(NSData *)privateKey - forSession:(NSString *)sessionId - inRoom:(NSString *)roomId +- (void)importKeysWithKeysBackupData:(MXKeysBackupData *)keysBackupData + privateKey:(NSData *)privateKey + keyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion + success:(void (^)(NSUInteger, NSUInteger))success + failure:(void (^)(NSError *))failure { id algorithm = [self getOrCreateKeyBackupAlgorithmFor:keyBackupVersion privateKey:privateKey]; - return [algorithm decryptKeyBackupData:keyBackupData forSession:sessionId inRoom:roomId]; -} - -- (void)importMegolmSessionDatas:(NSArray *)keys - backUp:(BOOL)backUp - success:(void (^)(NSUInteger, NSUInteger))success - failure:(void (^)(NSError * _Nonnull))failure -{ - [self.crypto importMegolmSessionDatas:keys backUp:backUp success:success failure:failure]; + + NSMutableArray *sessionDatas = [NSMutableArray array]; + + // Restore that data + NSUInteger sessionsFromHSCount = 0; + for (NSString *roomId in keysBackupData.rooms) + { + for (NSString *sessionId in keysBackupData.rooms[roomId].sessions) + { + sessionsFromHSCount++; + MXKeyBackupData *keyBackupData = keysBackupData.rooms[roomId].sessions[sessionId]; + MXMegolmSessionData *sessionData = [algorithm decryptKeyBackupData:keyBackupData forSession:sessionId inRoom:roomId]; + + if (sessionData) + { + [sessionDatas addObject:sessionData]; + } + } + } + + MXLogDebug(@"[MXNativeKeyBackupEngine] importKeysWithKeysBackupData: Decrypted %@ keys out of %@ from the backup store on the homeserver", @(sessionDatas.count), @(sessionsFromHSCount)); + + // Do not trigger a backup for them if they come from the backup version we are using + BOOL backUp = ![keyBackupVersion.version isEqualToString:self.keyBackupVersion.version]; + if (backUp) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] importKeysWithKeysBackupData: Those keys will be backed up to backup version: %@", self.keyBackupVersion.version); + } + + // Import them into the crypto store + [self.crypto importMegolmSessionDatas:sessionDatas backUp:backUp success:success failure:^(NSError *error) { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; } #pragma mark - Private methods - -- (id)getOrCreateKeyBackupAlgorithmFor:(MXKeyBackupVersion*)keyBackupVersion privateKey:(NSData*)privateKey +- (id)getOrCreateKeyBackupAlgorithmFor:(MXKeyBackupVersion *)keyBackupVersion privateKey:(NSData *)privateKey { if (self.enabled && [self.keyBackupVersion.JSONDictionary isEqualToDictionary:keyBackupVersion.JSONDictionary] @@ -595,7 +613,7 @@ - (void)importMegolmSessionDatas:(NSArray *)keys return nil; } NSError *error; - id authData = [self megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; + id authData = [self authDataFromKeyBackupVersion:keyBackupVersion error:&error]; if (error) { MXLogError(@"[MXNativeKeyBackupEngine] getOrCreateKeyBackupAlgorithmFor: invalid auth data"); @@ -606,20 +624,33 @@ - (void)importMegolmSessionDatas:(NSArray *)keys }]; } -- (OLMPkDecryption*)pkDecryptionFromRecoveryKey:(NSString*)recoveryKey error:(NSError **)error +- (void)validateKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion { - // Extract the private key - NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:error]; - - // Built the PK decryption with it - OLMPkDecryption *decryption; - if (privateKey) + // Check private keys + if (self.privateKey) { - decryption = [OLMPkDecryption new]; - [decryption setPrivateKey:privateKey error:error]; - } + Class algorithmClass = AlgorithmClassesByName[keyBackupVersion.algorithm]; + if (algorithmClass == NULL) + { + NSString *message = [NSString stringWithFormat:@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: unknown algorithm: %@", keyBackupVersion.algorithm]; + MXLogError(message); + return; + } + if (![algorithmClass checkBackupVersion:keyBackupVersion]) + { + MXLogError(@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: invalid backup data returned"); + return; + } - return decryption; + NSData *privateKey = self.privateKey; + NSError *error; + BOOL keyMatches = [algorithmClass keyMatches:privateKey withAuthData:keyBackupVersion.authData error:&error]; + if (error || !keyMatches) + { + MXLogDebug(@"[MXNativeKeyBackupEngine] validateKeyBackupVersion: -> private key does not match: %@, will be removed", error); + [self.crypto.store deleteSecretWithSecretId:MXSecretId.keyBackup]; + } + } } @end diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h index 28eba65911..b79b041e46 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h @@ -223,14 +223,6 @@ FOUNDATION_EXPORT NSString *const kMXKeyBackupDidStateChangeNotification; #pragma mark - Backup restoring -/** - Check if a key is a valid recovery key. - - @param recoveryKey the string to valid. - @return YES if valid - */ -+ (BOOL)isValidRecoveryKey:(NSString*)recoveryKey; - /** Restore a backup with a recovery key from a given backup version stored on the homeserver. diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m index d372ef2f1a..46bc2a89a7 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m @@ -121,7 +121,7 @@ - (void)checkAndStartWithKeyBackupVersion:(nullable MXKeyBackupVersion*)keyBacku return; } - MXKeyBackupVersionTrust *trustInfo = [self.engine trustForKeyBackupVersionFromCryptoQueue:keyBackupVersion]; + MXKeyBackupVersionTrust *trustInfo = [self.engine trustForKeyBackupVersion:keyBackupVersion]; if (trustInfo.usable) { @@ -134,8 +134,6 @@ - (void)checkAndStartWithKeyBackupVersion:(nullable MXKeyBackupVersion*)keyBacku MXLogDebug(@"[MXKeyBackup] -> clean the previously used version(%@)", versionInStore); [self resetKeyBackupData]; } - - [self.engine validateKeyBackupVersion:keyBackupVersion]; MXLogDebug(@"[MXKeyBackup] -> enabling key backups"); [self enableKeyBackup:keyBackupVersion]; @@ -163,7 +161,7 @@ - (void)checkAndStartWithKeyBackupVersion:(nullable MXKeyBackupVersion*)keyBacku - (NSError*)enableKeyBackup:(MXKeyBackupVersion*)version { NSError *error; - if (![self.engine enableBackupWithVersion:version error:&error]) + if (![self.engine enableBackupWithKeyBackupVersion:version error:&error]) { return error; } @@ -248,26 +246,13 @@ - (void)sendKeyBackup self.state = MXKeyBackupStateBackingUp; - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 2 - Encrypting keys"); - - MXKeyBackupPayload *payload = [self.engine roomKeysBackupPayload]; - if (!payload) - { - MXLogError(@"[MXKeyBackup] sendKeyBackup: Cannot get room key backups"); - return; - } - - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 4 - Sending request"); + MXLogDebug(@"[MXKeyBackup] sendKeyBackup: Backing up keys"); - // Make the request MXWeakify(self); - [self.restClient sendKeysBackup:payload.backupData version:_keyBackupVersion.version success:^(NSDictionary *JSONResponse){ + [self.engine backupKeysWithSuccess:^{ MXStrongifyAndReturnIfNil(self); - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 5a - Request complete"); - - // Mark keys as backed up - payload.completion(JSONResponse); + MXLogDebug(@"[MXKeyBackup] sendKeyBackup: Request complete"); if (!self.engine.hasKeysToBackup) { @@ -285,7 +270,7 @@ - (void)sendKeyBackup } failure:^(NSError *error) { MXStrongifyAndReturnIfNil(self); - MXLogDebug(@"[MXKeyBackup] sendKeyBackup: 5b - sendKeysBackup failed. Error: %@", error); + MXLogDebug(@"[MXKeyBackup] sendKeyBackup: backupRoomKeysSuccess failed. Error: %@", error); void (^backupAllGroupSessionsFailure)(NSError *error) = self->backupAllGroupSessionsFailure; @@ -344,21 +329,9 @@ - (void)restoreKeyBackupAutomaticallyWithPrivateKey:(void (^)(void))onComplete } // Check private keys - if (!self.engine.privateKey) - { - MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error: No private key"); - onComplete(); - return; - } - - // Check private keys validity - NSData *privateKey = self.engine.privateKey; - NSError *error; - BOOL keyMatches = [self.engine isValidPrivateKey:privateKey error:&error]; - if (!keyMatches) + if (!self.engine.hasValidPrivateKey) { - MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error: Private key does not match: %@", error); - [self.engine deletePrivateKey]; + MXLogDebug(@"[MXKeyBackup] restoreKeyBackupAutomatically. Error: No valid private key"); onComplete(); return; } @@ -449,14 +422,17 @@ - (MXHTTPOperation*)createKeyBackupVersion:(MXMegolmBackupCreationInfo*)keyBacku keyBackupVersion.authData = keyBackupCreationInfo.authData.JSONDictionary; MXHTTPOperation *operation2 = [self.restClient createKeyBackupVersion:keyBackupVersion success:^(NSString *version) { + keyBackupVersion.version = version; // Disable current backup [self.engine disableBackup]; // Store the fresh new private key - [self.engine saveRecoveryKey:keyBackupCreationInfo.recoveryKey]; - - keyBackupVersion.version = version; + NSData *privateKey = [self privateKeyForRecoveryKey:keyBackupCreationInfo.recoveryKey]; + if (privateKey) + { + [self.engine savePrivateKey:privateKey version:keyBackupVersion.version]; + } NSError *error = [self enableKeyBackup:keyBackupVersion]; @@ -712,12 +688,16 @@ - (void)backupProgress:(void (^)(NSProgress *backupProgress))backupProgress #pragma mark - Backup restoring -+ (BOOL)isValidRecoveryKey:(NSString*)recoveryKey +- (NSData *)privateKeyForRecoveryKey:(NSString *)recoveryKey { NSError *error; - NSData *privateKeyOut = [MXRecoveryKey decode:recoveryKey error:&error]; - - return !error && privateKeyOut; + NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:&error]; + if (error || !privateKey) + { + MXLogErrorDetails(@"[MXKeyBackup] privateKeyForRecoveryKey: Cannot create private key from recovery key: %@", error); + return nil; + } + return privateKey; } - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion @@ -737,9 +717,8 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion // Check if the recovery is valid before going any further NSError *error; - BOOL isValidRecoveryKey = [self.engine isValidRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; - NSData *privateKey = [MXRecoveryKey decode:recoveryKey error:&error]; - if (error || !isValidRecoveryKey || !privateKey) + NSData *privateKey = [self.engine validPrivateKeyForRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; + if (error || !privateKey) { MXLogDebug(@"[MXKeyBackup] restoreKeyBackup: Invalid recovery key. Error: %@", error); if (failure) @@ -754,9 +733,10 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion MXHTTPOperation *operation2 = [self restoreKeyBackup:keyBackupVersion withPrivateKey:privateKey room:roomId session:sessionId success:^(NSUInteger total, NSUInteger imported) { // Catch the private key from the recovery key and store it locally - if ([self.keyBackupVersion.version isEqualToString:keyBackupVersion.version]) + NSData *privateKey = [self privateKeyForRecoveryKey:recoveryKey]; + if ([self.keyBackupVersion.version isEqualToString:keyBackupVersion.version] && privateKey) { - [self.engine saveRecoveryKey:recoveryKey]; + [self.engine savePrivateKey:privateKey version:keyBackupVersion.version]; } if (success) @@ -782,49 +762,11 @@ - (MXHTTPOperation*)restoreKeyBackup:(MXKeyBackupVersion*)keyBackupVersion MXWeakify(self); return [self keyBackupForSession:sessionId inRoom:roomId version:keyBackupVersion.version success:^(MXKeysBackupData *keysBackupData) { MXStrongifyAndReturnIfNil(self); - - NSMutableArray *sessionDatas = [NSMutableArray array]; - - // Restore that data - NSUInteger sessionsFromHSCount = 0; - for (NSString *roomId in keysBackupData.rooms) - { - for (NSString *sessionId in keysBackupData.rooms[roomId].sessions) - { - sessionsFromHSCount++; - MXKeyBackupData *keyBackupData = keysBackupData.rooms[roomId].sessions[sessionId]; - MXMegolmSessionData *sessionData = [self.engine decryptKeyBackupData:keyBackupData - keyBackupVersion:keyBackupVersion - privateKey:privateKey - forSession:sessionId - inRoom:roomId]; - - if (sessionData) - { - [sessionDatas addObject:sessionData]; - } - } - } - - MXLogDebug(@"[MXKeyBackup] restoreKeyBackup: Decrypted %@ keys out of %@ from the backup store on the homeserver", @(sessionDatas.count), @(sessionsFromHSCount)); - - // Do not trigger a backup for them if they come from the backup version we are using - BOOL backUp = ![keyBackupVersion.version isEqualToString:self.keyBackupVersion.version]; - if (backUp) - { - MXLogDebug(@"[MXKeyBackup] restoreKeyBackup: Those keys will be backed up to backup version: %@", self.keyBackupVersion.version); - } - - // Import them into the crypto store - [self.engine importMegolmSessionDatas:sessionDatas backUp:backUp success:success failure:^(NSError *error) { - if (failure) - { - dispatch_async(dispatch_get_main_queue(), ^{ - failure(error); - }); - } - }]; - + [self.engine importKeysWithKeysBackupData:keysBackupData + privateKey:privateKey + keyBackupVersion:keyBackupVersion + success:success + failure:failure]; } failure:^(NSError *error) { if (failure) { @@ -887,11 +829,9 @@ - (MXHTTPOperation*)restoreUsingPrivateKeyKeyBackup:(MXKeyBackupVersion*)keyBack dispatch_async(cryptoQueue, ^{ MXStrongifyAndReturnIfNil(self); - NSData *privateKey = self.engine.privateKey; - NSError *error; - if (![self.engine isValidPrivateKey:privateKey forKeyBackupVersion:keyBackupVersion error:&error]) + if (![self.engine hasValidPrivateKeyForKeyBackupVersion:keyBackupVersion]) { - MXLogDebug(@"[MXKeyBackup] restoreUsingPrivateKeyKeyBackup. Error: Private key does not match: %@, for: %@", error, keyBackupVersion); + MXLogDebug(@"[MXKeyBackup] restoreUsingPrivateKeyKeyBackup. Error: Private key does not match: for: %@", keyBackupVersion); if (failure) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -906,7 +846,7 @@ - (MXHTTPOperation*)restoreUsingPrivateKeyKeyBackup:(MXKeyBackupVersion*)keyBack } // Launch the restore - MXHTTPOperation *operation2 = [self restoreKeyBackup:keyBackupVersion withPrivateKey:privateKey room:roomId session:sessionId success:success failure:failure]; + MXHTTPOperation *operation2 = [self restoreKeyBackup:keyBackupVersion withPrivateKey:self.engine.privateKey room:roomId session:sessionId success:success failure:failure]; [operation mutateTo:operation2]; }); @@ -924,7 +864,7 @@ - (void)trustForKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersion onComple { dispatch_async(cryptoQueue, ^{ - MXKeyBackupVersionTrust *keyBackupVersionTrust = [self.engine trustForKeyBackupVersionFromCryptoQueue:keyBackupVersion]; + MXKeyBackupVersionTrust *keyBackupVersionTrust = [self.engine trustForKeyBackupVersion:keyBackupVersion]; dispatch_async(dispatch_get_main_queue(), ^{ onComplete(keyBackupVersionTrust); @@ -949,7 +889,7 @@ - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersio // Get auth data to update it NSError *error; - id authData = [self.engine megolmBackupAuthDataFromKeyBackupVersion:keyBackupVersion error:&error]; + id authData = [self.engine authDataFromKeyBackupVersion:keyBackupVersion error:&error]; if (error) { MXLogDebug(@"[MXKeyBackup] trustKeyBackupVersion:trust: Key backup is missing required data"); @@ -1037,8 +977,8 @@ - (MXHTTPOperation *)trustKeyBackupVersion:(MXKeyBackupVersion *)keyBackupVersio MXStrongifyAndReturnIfNil(self); NSError *error; - [self.engine isValidRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; - if (!error) + NSData *privateKey = [self.engine validPrivateKeyForRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; + if (!error && privateKey) { MXHTTPOperation *operation2 = [self trustKeyBackupVersion:keyBackupVersion trust:YES success:success failure:failure]; [operation mutateTo:operation2]; @@ -1116,7 +1056,8 @@ - (void)requestPrivateKeysToDeviceIds:(nullable NSArray*)deviceIds MXLogDebug(@"[MXKeyBackup] requestPrivateKeysToDeviceIds: Got key. isSecretValid: %@", @(isSecretValid)); if (isSecretValid) { - [self.engine savePrivateKey:secret]; + NSData *privateKey = [MXBase64Tools dataFromBase64:secret]; + [self.engine savePrivateKey:privateKey version:self.keyBackupVersion.version]; onPrivateKeysReceived(); } return isSecretValid; @@ -1126,7 +1067,16 @@ - (void)requestPrivateKeysToDeviceIds:(nullable NSArray*)deviceIds - (BOOL)isSecretValid:(NSString*)secret forKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion { NSData *privateKey = [MXBase64Tools dataFromBase64:secret]; - return [self.engine isValidPrivateKey:privateKey forKeyBackupVersion:keyBackupVersion error:nil]; + NSString *recoveryKey = [MXRecoveryKey encode:privateKey]; + + NSError *error; + NSData *validPrivateKey = [self.engine validPrivateKeyForRecoveryKey:recoveryKey forKeyBackupVersion:keyBackupVersion error:&error]; + if (error) + { + MXLogErrorDetails(@"[MXKeyBackup] isSecretValid: Error %@", error); + return NO; + } + return [privateKey isEqualToData:validPrivateKey]; } #pragma mark - Backup state diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h index af24a8767d..f7931cd265 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h @@ -32,7 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @param engine backup engine that stores and manages keys @param restClient rest client to perform http requests - @param secretShareManager manages of secrets hsaring + @param secretShareManager manages of secrets sharing @param queue dispatch queue to perform all operations on */ - (instancetype)initWithEngine:(id)engine diff --git a/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h index 77c2f359e5..b60d4b68f5 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h +++ b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h @@ -58,6 +58,11 @@ typedef enum : NSUInteger */ + (nullable NSData *)decode:(NSString*)recoveryKey error:(NSError * _Nullable *)error; +/** + Check to see whether the recovery key is valid, i.e. can be used to create a private key + */ ++ (BOOL)isValidRecoveryKey:(NSString*)recoveryKey; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m index dbbb2d54cb..59a084960d 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m +++ b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m @@ -179,4 +179,12 @@ + (NSData *)decodeBase58:(NSString *)base58 return data; } ++ (BOOL)isValidRecoveryKey:(NSString *)recoveryKey +{ + NSError *error; + NSData *privateKeyOut = [MXRecoveryKey decode:recoveryKey error:&error]; + + return !error && privateKeyOut; +} + @end diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index a3472b9566..b6643ef774 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -78,7 +78,7 @@ NSTimeInterval kMXCryptoUploadOneTimeKeysPeriod = 60.0; // one minute NSTimeInterval kMXCryptoMinForceSessionPeriod = 3600.0; // one hour -@interface MXCrypto () +@interface MXCrypto () { // MXEncrypting instance for each room. NSMutableDictionary> *roomEncryptors; @@ -2049,8 +2049,6 @@ - (instancetype)initWithMatrixSession:(MXSession*)matrixSession cryptoQueue:(dis _crossSigning = [[MXCrossSigning alloc] initWithCrypto:self]; - _recoveryService = [[MXRecoveryService alloc] initWithCrypto:self]; - if ([MXSDKOptions sharedInstance].enableKeyBackupWhenStartingMXCrypto) { id engine = [[MXNativeKeyBackupEngine alloc] initWithCrypto:self]; @@ -2060,6 +2058,14 @@ - (instancetype)initWithMatrixSession:(MXSession*)matrixSession cryptoQueue:(dis queue:_cryptoQueue]; } + MXRecoveryServiceDependencies *dependencies = [[MXRecoveryServiceDependencies alloc] initWithCredentials:_mxSession.matrixRestClient.credentials + backup:_backup + secretStorage:_secretStorage + secretStore:_store + crossSigning:_crossSigning + cryptoQueue:_cryptoQueue]; + _recoveryService = [[MXRecoveryService alloc] initWithDependencies:dependencies delegate:self]; + cryptoMigration = [[MXCryptoMigration alloc] initWithCrypto:self]; lastNewSessionForcedDates = [MXUsersDevicesMap new]; diff --git a/MatrixSDK/Crypto/Recovery/MXRecoveryService.h b/MatrixSDK/Crypto/Recovery/MXRecoveryService.h index b0a8d7d9c3..26482abc80 100644 --- a/MatrixSDK/Crypto/Recovery/MXRecoveryService.h +++ b/MatrixSDK/Crypto/Recovery/MXRecoveryService.h @@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN +@class MXRecoveryServiceDependencies; #pragma mark - Constants @@ -35,6 +36,12 @@ typedef NS_ENUM(NSInteger, MXRecoveryServiceErrorCode) MXRecoveryServiceBadRecoveryKeyFormatErrorCode, }; +@protocol MXRecoveryServiceDelegate +- (void)setUserVerification:(BOOL)verificationStatus forUser:(NSString*)userId + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure; +@end + /** `MXRecoveryService` manages the backup of secrets/keys used by `MXCrypto`. @@ -56,6 +63,9 @@ typedef NS_ENUM(NSInteger, MXRecoveryServiceErrorCode) */ @property (nonatomic, copy) NSArray *supportedSecrets; +- (instancetype)initWithDependencies:(MXRecoveryServiceDependencies *)dependencies + delegate:(id)delegate; + #pragma mark - Recovery setup diff --git a/MatrixSDK/Crypto/Recovery/MXRecoveryService.m b/MatrixSDK/Crypto/Recovery/MXRecoveryService.m index bd2bca4b9c..b04d7c9783 100644 --- a/MatrixSDK/Crypto/Recovery/MXRecoveryService.m +++ b/MatrixSDK/Crypto/Recovery/MXRecoveryService.m @@ -24,6 +24,7 @@ #import "MXAesHmacSha2.h" #import "MXTools.h" #import "NSArray+MatrixSDK.h" +#import "MatrixSDKSwiftHeader.h" #pragma mark - Constants @@ -32,31 +33,26 @@ @interface MXRecoveryService () -{ -} -@property (nonatomic, readonly, weak) MXCrypto *crypto; -@property (nonatomic, readonly, weak) id cryptoStore; -@property (nonatomic, readonly, weak) MXSecretStorage *secretStorage; +@property (nonatomic, strong) MXRecoveryServiceDependencies *dependencies; +@property (nonatomic, weak) id delegate; @end @implementation MXRecoveryService -#pragma mark - SDK-Private methods - +#pragma mark - Public methods - -- (instancetype)initWithCrypto:(MXCrypto *)crypto; +- (instancetype)initWithDependencies:(MXRecoveryServiceDependencies *)dependencies + delegate:(id)delegate { - NSParameterAssert(crypto.store && crypto.secretStorage); - + self = [super init]; if (self) { - _crypto = crypto; - _cryptoStore = crypto.store; - _secretStorage = crypto.secretStorage; - + _dependencies = dependencies; + _delegate = delegate; _supportedSecrets = @[ MXSecretId.crossSigningMaster, MXSecretId.crossSigningSelfSigning, @@ -67,14 +63,11 @@ - (instancetype)initWithCrypto:(MXCrypto *)crypto; return self; } - -#pragma mark - Public methods - - #pragma mark - Recovery setup - (nullable NSString*)recoveryId { - return _secretStorage.defaultKeyId; + return self.dependencies.secretStorage.defaultKeyId; } - (BOOL)hasRecovery @@ -84,7 +77,7 @@ - (BOOL)hasRecovery - (BOOL)usePassphrase { - MXSecretStorageKeyContent *keyContent = [_secretStorage keyWithKeyId:self.recoveryId]; + MXSecretStorageKeyContent *keyContent = [self.dependencies.secretStorage keyWithKeyId:self.recoveryId]; if (!keyContent) { MXLogError(@"[MXRecoveryService] usePassphrase: no recovery key exists"); @@ -123,7 +116,7 @@ - (void)deleteRecoveryWithSuccess:(void (^)(void))success failure:(void (^)(NSEr MXLogDebug(@"[MXRecoveryService] deleteRecovery: Remove secret %@", secretId); - [_secretStorage deleteSecretWithSecretId:secretId success:^{ + [self.dependencies.secretStorage deleteSecretWithSecretId:secretId success:^{ dispatch_group_leave(dispatchGroup); } failure:^(NSError * _Nonnull anError) { MXLogDebug(@"[MXRecoveryService] deleteRecovery: Failed to remove %@. Error: %@", secretId, anError); @@ -149,7 +142,7 @@ - (void)deleteRecoveryWithSuccess:(void (^)(void))success failure:(void (^)(NSEr if (ssssKeyId) { - [self.secretStorage deleteKeyWithKeyId:ssssKeyId success:success failure:failure]; + [self.dependencies.secretStorage deleteKeyWithKeyId:ssssKeyId success:success failure:failure]; } else { @@ -164,7 +157,7 @@ - (void)deleteKeyBackupWithSuccess:(void (^)(void))success { MXLogDebug(@"[MXRecoveryService] deleteKeyBackup"); - MXKeyBackup *keyBackup = self.crypto.backup; + MXKeyBackup *keyBackup = self.dependencies.backup; if (!keyBackup) { success(); @@ -187,7 +180,7 @@ - (void)deleteKeyBackupWithSuccess:(void (^)(void))success - (void)checkPrivateKey:(NSData*)privateKey complete:(void (^)(BOOL match))complete { - MXSecretStorageKeyContent *keyContent = [_secretStorage keyWithKeyId:self.recoveryId]; + MXSecretStorageKeyContent *keyContent = [self.dependencies.secretStorage keyWithKeyId:self.recoveryId]; if (!keyContent) { MXLogError(@"[MXRecoveryService] checkPrivateKey: no recovery key exists"); @@ -195,7 +188,7 @@ - (void)checkPrivateKey:(NSData*)privateKey complete:(void (^)(BOOL match))compl return; } - [_secretStorage checkPrivateKey:privateKey withKey:keyContent complete:complete]; + [self.dependencies.secretStorage checkPrivateKey:privateKey withKey:keyContent complete:complete]; } @@ -203,7 +196,7 @@ - (void)checkPrivateKey:(NSData*)privateKey complete:(void (^)(BOOL match))compl - (BOOL)hasSecretWithSecretId:(NSString*)secretId { - return [_secretStorage hasSecretWithSecretId:secretId withSecretStorageKeyId:self.recoveryId]; + return [self.dependencies.secretStorage hasSecretWithSecretId:secretId withSecretStorageKeyId:self.recoveryId]; } - (NSArray*)secretsStoredInRecovery @@ -225,7 +218,7 @@ - (BOOL)hasSecretWithSecretId:(NSString*)secretId - (BOOL)hasSecretLocally:(NSString*)secretId { - return ([_cryptoStore secretWithSecretId:secretId] != nil); + return ([self.dependencies.secretStore secretWithSecretId:secretId] != nil); } - (NSArray*)secretsStoredLocally @@ -317,11 +310,11 @@ - (void)createRecoveryForSecrets:(nullable NSArray*)secrets failure:(void (^)(NSError *error))failure { MXWeakify(self); - [_secretStorage createKeyWithKeyId:nil keyName:nil privateKey:privateKey success:^(MXSecretStorageKeyCreationInfo * _Nonnull keyCreationInfo) { - MXStrongifyAndReturnIfNil(self); + [self.dependencies.secretStorage createKeyWithKeyId:nil keyName:nil privateKey:privateKey success:^(MXSecretStorageKeyCreationInfo * _Nonnull keyCreationInfo) { // Set this recovery as the default SSSS key id - [self.secretStorage setAsDefaultKeyWithKeyId:keyCreationInfo.keyId success:^{ + [self.dependencies.secretStorage setAsDefaultKeyWithKeyId:keyCreationInfo.keyId success:^{ + MXStrongifyAndReturnIfNil(self); [self updateRecoveryForSecrets:secrets withPrivateKey:keyCreationInfo.privateKey success:^{ success(keyCreationInfo); @@ -341,11 +334,11 @@ - (void)createRecoveryForSecrets:(nullable NSArray*)secrets failure:(void (^)(NSError *error))failure { MXWeakify(self); - [_secretStorage createKeyWithKeyId:nil keyName:nil passphrase:passphrase success:^(MXSecretStorageKeyCreationInfo * _Nonnull keyCreationInfo) { - MXStrongifyAndReturnIfNil(self); + [self.dependencies.secretStorage createKeyWithKeyId:nil keyName:nil passphrase:passphrase success:^(MXSecretStorageKeyCreationInfo * _Nonnull keyCreationInfo) { // Set this recovery as the default SSSS key id - [self.secretStorage setAsDefaultKeyWithKeyId:keyCreationInfo.keyId success:^{ + [self.dependencies.secretStorage setAsDefaultKeyWithKeyId:keyCreationInfo.keyId success:^{ + MXStrongifyAndReturnIfNil(self); [self updateRecoveryForSecrets:secrets withPrivateKey:keyCreationInfo.privateKey success:^{ success(keyCreationInfo); @@ -364,7 +357,7 @@ - (void)createKeyBackupWithSuccess:(void (^)(void))success { MXLogDebug(@"[MXRecoveryService] createKeyBackup"); - MXKeyBackup *keyBackup = self.crypto.backup; + MXKeyBackup *keyBackup = self.dependencies.backup; if (!keyBackup) { success(); @@ -410,7 +403,7 @@ - (void)createKeyBackupWithSuccess:(void (^)(void))success // If a backup already exists, make sure we have the private key locally if (keyBackup.keyBackupVersion) { - if ([self.cryptoStore secretWithSecretId:MXSecretId.keyBackup]) + if ([self.dependencies.secretStore secretWithSecretId:MXSecretId.keyBackup]) { MXLogDebug(@"[MXRecoveryService] createKeyBackup: Reuse private key of existing one"); success(); @@ -485,12 +478,12 @@ - (void)updateRecoveryForSecrets:(nullable NSArray*)secrets for (NSString *secretId in secretsToStore) { - NSString *secret = [self.cryptoStore secretWithSecretId:secretId]; + NSString *secret = [self.dependencies.secretStore secretWithSecretId:secretId]; if (secret) { dispatch_group_enter(dispatchGroup); - [self.secretStorage storeSecret:secret withSecretId:secretId withSecretStorageKeys:keys success:^(NSString * _Nonnull secretId) { + [self.dependencies.secretStorage storeSecret:secret withSecretId:secretId withSecretStorageKeys:keys success:^(NSString * _Nonnull secretId) { dispatch_group_leave(dispatchGroup); } failure:^(NSError * _Nonnull anError) { MXLogDebug(@"[MXRecoveryService] updateRecovery: Failed to store %@. Error: %@", secretId, anError); @@ -558,19 +551,19 @@ - (void)recoverSecrets:(nullable NSArray*)secrets { dispatch_group_enter(dispatchGroup); - [_secretStorage secretWithSecretId:secretId withSecretStorageKeyId:secretStorageKeyId privateKey:privateKey success:^(NSString * _Nonnull unpaddedBase64Secret) { + [self.dependencies.secretStorage secretWithSecretId:secretId withSecretStorageKeyId:secretStorageKeyId privateKey:privateKey success:^(NSString * _Nonnull unpaddedBase64Secret) { NSString *secret = unpaddedBase64Secret; // Validate the secret before storing it if ([self checkSecret:secret withSecretId:secretId]) { - if (![secret isEqualToString:[self.cryptoStore secretWithSecretId:secretId]]) + if (![secret isEqualToString:[self.dependencies.secretStore secretWithSecretId:secretId]]) { MXLogDebug(@"[MXRecoveryService] recoverSecrets: Recovered secret %@", secretId); [updatedSecrets addObject:secretId]; - [self.cryptoStore storeSecret:secret withSecretId:secretId]; + [self.dependencies.secretStore storeSecret:secret withSecretId:secretId]; } else { @@ -701,24 +694,24 @@ - (void)recoverServicesAssociatedWithSecrets:(nullable NSArray*)secre - (void)recoverKeyBackupWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failure { - MXLogDebug(@"[MXRecoveryService] recoverKeyBackup: %@", self.crypto.backup.keyBackupVersion.version); + MXLogDebug(@"[MXRecoveryService] recoverKeyBackup: %@", self.dependencies.backup.keyBackupVersion.version); - MXKeyBackupVersion *keyBackupVersion = self.crypto.backup.keyBackupVersion; - NSString *secret = [self.crypto.store secretWithSecretId:MXSecretId.keyBackup]; + MXKeyBackupVersion *keyBackupVersion = self.dependencies.backup.keyBackupVersion; + NSString *secret = [self.dependencies.secretStore secretWithSecretId:MXSecretId.keyBackup]; if (keyBackupVersion && secret - && [self.crypto.backup isSecretValid:secret forKeyBackupVersion:keyBackupVersion]) + && [self.dependencies.backup isSecretValid:secret forKeyBackupVersion:keyBackupVersion]) { // Restore the backup in background // It will take time - [self.crypto.backup restoreUsingPrivateKeyKeyBackup:keyBackupVersion room:nil session:nil success:^(NSUInteger total, NSUInteger imported) { + [self.dependencies.backup restoreUsingPrivateKeyKeyBackup:keyBackupVersion room:nil session:nil success:^(NSUInteger total, NSUInteger imported) { MXLogDebug(@"[MXRecoveryService] recoverKeyBackup: Backup is restored!"); } failure:^(NSError * _Nonnull error) { MXLogDebug(@"[MXRecoveryService] recoverKeyBackup: restoreUsingPrivateKeyKeyBackup failed: %@", error); }]; // Check if the service really needs to be started - if (self.crypto.backup.enabled) + if (self.dependencies.backup.enabled) { MXLogDebug(@"[MXRecoveryService] recoverKeyBackup: Key backup is already running"); success(); @@ -726,7 +719,7 @@ - (void)recoverKeyBackupWithSuccess:(void (^)(void))success } // Trust the current backup to start backuping keys to it - [self.crypto.backup trustKeyBackupVersion:keyBackupVersion trust:YES success:^{ + [self.dependencies.backup trustKeyBackupVersion:keyBackupVersion trust:YES success:^{ MXLogDebug(@"[MXRecoveryService] recoverKeyBackup: Current backup is now trusted"); success(); } failure:^(NSError * _Nonnull error) { @@ -745,10 +738,10 @@ - (void)recoverCrossSigningWithSuccess:(void (^)(void))success { MXLogDebug(@"[MXRecoveryService] recoverCrossSigning"); - [self.crypto.crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { + [self.dependencies.crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { // Check if the service really needs to be started - if (self.crypto.crossSigning.canCrossSign) + if (self.dependencies.crossSigning.canCrossSign) { MXLogDebug(@"[MXRecoveryService] recoverCrossSigning: Cross-signing is already up"); success(); @@ -756,14 +749,14 @@ - (void)recoverCrossSigningWithSuccess:(void (^)(void))success } // Mark our user MSK as verified locally - [self.crypto setUserVerification:YES forUser:self.crypto.mxSession.myUserId success:^{ + [self.delegate setUserVerification:YES forUser:self.dependencies.credentials.userId success:^{ // Cross sign our current device - [self.crypto.crossSigning crossSignDeviceWithDeviceId:self.crypto.mxSession.myDeviceId success:^{ + [self.dependencies.crossSigning crossSignDeviceWithDeviceId:self.dependencies.credentials.deviceId success:^{ // And update the state - [self.crypto.crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { - MXLogDebug(@"[MXRecoveryService] recoverCrossSigning: Cross-signing is up. State: %@", @(self.crypto.crossSigning.state)); + [self.dependencies.crossSigning refreshStateWithSuccess:^(BOOL stateUpdated) { + MXLogDebug(@"[MXRecoveryService] recoverCrossSigning: Cross-signing is up. State: %@", @(self.dependencies.crossSigning.state)); success(); } failure:^(NSError *error) { MXLogDebug(@"[MXRecoveryService] recoverCrossSigning: refreshStateWithSuccess 2 failed: %@", error); @@ -817,7 +810,7 @@ - (void)privateKeyFromPassphrase:(NSString*)passphrase return; } - MXSecretStorageKeyContent *keyContent = [_secretStorage keyWithKeyId:self.recoveryId]; + MXSecretStorageKeyContent *keyContent = [self.dependencies.secretStorage keyWithKeyId:self.recoveryId]; if (!keyContent.passphrase) { // No passphrase @@ -832,7 +825,7 @@ - (void)privateKeyFromPassphrase:(NSString*)passphrase // Go to a queue for derivating the passphrase into a recovery key - dispatch_async(_crypto.cryptoQueue, ^{ + dispatch_async(self.dependencies.cryptoQueue, ^{ NSError *error; NSData *privateKey = [MXKeyBackupPassword retrievePrivateKeyWithPassword:passphrase @@ -864,10 +857,10 @@ - (BOOL)checkSecret:(NSString*)secret withSecretId:(NSString*)secretId if ([secretId isEqualToString:MXSecretId.keyBackup]) { - MXKeyBackupVersion *keyBackupVersion = self.crypto.backup.keyBackupVersion; + MXKeyBackupVersion *keyBackupVersion = self.dependencies.backup.keyBackupVersion; if (keyBackupVersion) { - valid = [self.crypto.backup isSecretValid:secret forKeyBackupVersion:keyBackupVersion]; + valid = [self.dependencies.backup isSecretValid:secret forKeyBackupVersion:keyBackupVersion]; } else { @@ -876,10 +869,10 @@ - (BOOL)checkSecret:(NSString*)secret withSecretId:(NSString*)secretId } else if ([secretId isEqualToString:MXSecretId.crossSigningMaster]) { - MXCrossSigningInfo *crossSigningInfo = self.crypto.crossSigning.myUserCrossSigningKeys; + MXCrossSigningInfo *crossSigningInfo = self.dependencies.crossSigning.myUserCrossSigningKeys; if (crossSigningInfo) { - valid = [self.crypto.crossSigning isSecretValid:secret forPublicKeys:crossSigningInfo.masterKeys.keys]; + valid = [self.dependencies.crossSigning isSecretValid:secret forPublicKeys:crossSigningInfo.masterKeys.keys]; } else { @@ -888,10 +881,10 @@ - (BOOL)checkSecret:(NSString*)secret withSecretId:(NSString*)secretId } else if ([secretId isEqualToString:MXSecretId.crossSigningSelfSigning]) { - MXCrossSigningInfo *crossSigningInfo = self.crypto.crossSigning.myUserCrossSigningKeys; + MXCrossSigningInfo *crossSigningInfo = self.dependencies.crossSigning.myUserCrossSigningKeys; if (crossSigningInfo) { - valid = [self.crypto.crossSigning isSecretValid:secret forPublicKeys:crossSigningInfo.selfSignedKeys.keys]; + valid = [self.dependencies.crossSigning isSecretValid:secret forPublicKeys:crossSigningInfo.selfSignedKeys.keys]; } else { @@ -900,10 +893,10 @@ - (BOOL)checkSecret:(NSString*)secret withSecretId:(NSString*)secretId } else if ([secretId isEqualToString:MXSecretId.crossSigningUserSigning]) { - MXCrossSigningInfo *crossSigningInfo = self.crypto.crossSigning.myUserCrossSigningKeys; + MXCrossSigningInfo *crossSigningInfo = self.dependencies.crossSigning.myUserCrossSigningKeys; if (crossSigningInfo) { - valid = [self.crypto.crossSigning isSecretValid:secret forPublicKeys:crossSigningInfo.userSignedKeys.keys]; + valid = [self.dependencies.crossSigning isSecretValid:secret forPublicKeys:crossSigningInfo.userSignedKeys.keys]; } else { diff --git a/MatrixSDK/Crypto/Recovery/MXRecoveryServiceDependencies.swift b/MatrixSDK/Crypto/Recovery/MXRecoveryServiceDependencies.swift new file mode 100644 index 0000000000..a2b3547367 --- /dev/null +++ b/MatrixSDK/Crypto/Recovery/MXRecoveryServiceDependencies.swift @@ -0,0 +1,43 @@ +// +// 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 MXRecoveryServiceDependencies: NSObject { + public let credentials: MXCredentials + public let backup: MXKeyBackup? + public let secretStorage: MXSecretStorage + public let secretStore: MXCryptoSecretStore + public let crossSigning: MXCrossSigning + public let cryptoQueue: DispatchQueue + + public init( + credentials: MXCredentials, + backup: MXKeyBackup?, + secretStorage: MXSecretStorage, + secretStore: MXCryptoSecretStore, + crossSigning: MXCrossSigning, + cryptoQueue: DispatchQueue + ) { + self.credentials = credentials + self.backup = backup + self.secretStorage = secretStorage + self.secretStore = secretStore + self.crossSigning = crossSigning + self.cryptoQueue = cryptoQueue + } +} diff --git a/MatrixSDK/Crypto/Recovery/MXRecoveryService_Private.h b/MatrixSDK/Crypto/Recovery/MXRecoveryService_Private.h index 214fd7cad0..b1ac0ee91a 100644 --- a/MatrixSDK/Crypto/Recovery/MXRecoveryService_Private.h +++ b/MatrixSDK/Crypto/Recovery/MXRecoveryService_Private.h @@ -22,13 +22,6 @@ NS_ASSUME_NONNULL_BEGIN @interface MXRecoveryService () -/** - Constructor. - - @param crypto the related 'MXCrypto' instance. - */ -- (instancetype)initWithCrypto:(MXCrypto *)crypto; - @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDKTests/MXBaseKeyBackupTests.m b/MatrixSDKTests/MXBaseKeyBackupTests.m index 22173e0585..a636f1ed8d 100644 --- a/MatrixSDKTests/MXBaseKeyBackupTests.m +++ b/MatrixSDKTests/MXBaseKeyBackupTests.m @@ -314,12 +314,12 @@ - (void)testIsValidRecoveryKey NSString *invalidRecoveryKey2 = @"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4f"; NSString *invalidRecoveryKey3 = @"EqTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d"; - XCTAssertTrue([MXKeyBackup isValidRecoveryKey:recoveryKey1]); - XCTAssertTrue([MXKeyBackup isValidRecoveryKey:recoveryKey2]); - XCTAssertTrue([MXKeyBackup isValidRecoveryKey:recoveryKey3]); - XCTAssertFalse([MXKeyBackup isValidRecoveryKey:invalidRecoveryKey1]); - XCTAssertFalse([MXKeyBackup isValidRecoveryKey:invalidRecoveryKey2]); - XCTAssertFalse([MXKeyBackup isValidRecoveryKey:invalidRecoveryKey3]); + XCTAssertTrue([MXRecoveryKey isValidRecoveryKey:recoveryKey1]); + XCTAssertTrue([MXRecoveryKey isValidRecoveryKey:recoveryKey2]); + XCTAssertTrue([MXRecoveryKey isValidRecoveryKey:recoveryKey3]); + XCTAssertFalse([MXRecoveryKey isValidRecoveryKey:invalidRecoveryKey1]); + XCTAssertFalse([MXRecoveryKey isValidRecoveryKey:invalidRecoveryKey2]); + XCTAssertFalse([MXRecoveryKey isValidRecoveryKey:invalidRecoveryKey3]); } /** From 91e6a86b59fa40f86bd64fa755a3ec329c38bf99 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 29 Sep 2022 16:30:16 +0300 Subject: [PATCH 15/30] Add `enableNewClientInformationFeature` sdk option --- MatrixSDK/MXSDKOptions.h | 7 +++++++ MatrixSDK/MXSDKOptions.m | 1 + MatrixSDK/MXSession.m | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/MatrixSDK/MXSDKOptions.h b/MatrixSDK/MXSDKOptions.h index a9877721f1..080e535f75 100644 --- a/MatrixSDK/MXSDKOptions.h +++ b/MatrixSDK/MXSDKOptions.h @@ -231,6 +231,13 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic) BOOL enableSymmetricBackup; +/** + Enable new client information feature. (https://github.com/vector-im/element-meta/pull/656) + + @remark NO by default + */ +@property (nonatomic) BOOL enableNewClientInformationFeature; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/MXSDKOptions.m b/MatrixSDK/MXSDKOptions.m index 6138216ce5..c48162e6cc 100644 --- a/MatrixSDK/MXSDKOptions.m +++ b/MatrixSDK/MXSDKOptions.m @@ -61,6 +61,7 @@ - (instancetype)init _enableGroupSessionCache = YES; _enableSymmetricBackup = NO; + _enableNewClientInformationFeature = NO; } return self; diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 369d2f8ace..efadbd7ae0 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -4563,6 +4563,11 @@ - (void)updateBreadcrumbsWithRoomWithId:(NSString *)roomId - (void)refreshClientInformationIfNeeded { + if (!MXSDKOptions.sharedInstance.enableNewClientInformationFeature) + { + [self removeClientInformationIfNeeded]; + return; + } NSString *bundleDisplayName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; NSString *name = [NSString stringWithFormat:@"%@ iOS", bundleDisplayName]; NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; @@ -4589,6 +4594,25 @@ - (void)refreshClientInformationIfNeeded } } +- (void)removeClientInformationIfNeeded +{ + NSString *type = [NSString stringWithFormat:@"%@.%@", kMXAccountDataTypeClientInformation, self.myDeviceId]; + NSDictionary *currentInfo = [self.accountData accountDataForEventType:type]; + if (currentInfo.count) + { + // remove current account data regarding client information + [self setAccountData:@{} forType:type success:^{ + MXLogDebug(@"[MXSession] removeClientInformationIfNeeded: removed successfully"); + } failure:^(NSError *error) { + MXLogDebug(@"[MXSession] removeClientInformationIfNeeded: remove failed: %@", error); + }]; + } + else + { + MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: no need to remove"); + } +} + #pragma mark - Homeserver information - (MXWellKnown *)homeserverWellknown { From 46db38dafb05fd9ff0e3d8503afa21803f3b4fce Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 29 Sep 2022 16:32:07 +0300 Subject: [PATCH 16/30] Add changelog --- changelog.d/pr-1588.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-1588.change diff --git a/changelog.d/pr-1588.change b/changelog.d/pr-1588.change new file mode 100644 index 0000000000..4adf7484f5 --- /dev/null +++ b/changelog.d/pr-1588.change @@ -0,0 +1 @@ +Add `enableNewClientInformationFeature` sdk option, disabled by default (PSG-799). From cc5acfc3c92f3f09e170e0f3aacb50bb1495a73c Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 22 Sep 2022 20:31:37 +0100 Subject: [PATCH 17/30] Key backups with Crypto V2 --- MatrixSDK.xcodeproj/project.pbxproj | 84 +++- .../CryptoMachine/MXCryptoMachine.swift | 123 +++++- .../CryptoMachine/MXCryptoProtocols.swift | 18 + .../CryptoMachine/MXCryptoRequests.swift | 30 ++ .../Engine/MXCryptoKeyBackupEngine.swift | 389 ++++++++++++++++++ MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h | 29 +- .../Crypto/KeyBackup/MXKeyBackup_Private.h | 26 -- MatrixSDK/Crypto/MXCryptoV2.swift | 70 +++- .../SecretStorage/MXCryptoSecretStoreV2.swift | 62 +++ .../Crypto/SecretStorage/MXSecretStorage.h | 8 + .../SecretStorage/MXSecretStorage_Private.h | 7 - .../MXKeyVerificationManagerV2.swift | 6 + MatrixSDK/MatrixSDK.h | 4 + .../CryptoMachine/MXCryptoProtocolStubs.swift | 42 +- .../MXCryptoRequestsUnitTests.swift | 25 ++ .../Data/MXKeyBackupVersion+Stub.swift | 39 ++ .../MXCryptoKeyBackupEngineUnitTests.swift | 214 ++++++++++ MatrixSDKTests/TestPlans/UnitTests.xctestplan | 1 + .../UnitTestsWithSanitizers.xctestplan | 1 + changelog.d/6769.change | 1 + 20 files changed, 1114 insertions(+), 65 deletions(-) create mode 100644 MatrixSDK/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngine.swift create mode 100644 MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift create mode 100644 MatrixSDKTests/Crypto/KeyBackup/Data/MXKeyBackupVersion+Stub.swift create mode 100644 MatrixSDKTests/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngineUnitTests.swift create mode 100644 changelog.d/6769.change diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 4c0e57a917..6e4b297791 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -639,7 +639,7 @@ 32FCAB4D19E578860049C555 /* MXRestClientTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FCAB4C19E578860049C555 /* MXRestClientTests.m */; }; 32FE41361D0AB7070060835E /* MXEnumConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FE41341D0AB7070060835E /* MXEnumConstants.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32FE41371D0AB7070060835E /* MXEnumConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FE41351D0AB7070060835E /* MXEnumConstants.m */; }; - 32FFB4F0217E146A00C96002 /* MXRecoveryKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FFB4EE217E146A00C96002 /* MXRecoveryKey.h */; }; + 32FFB4F0217E146A00C96002 /* MXRecoveryKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FFB4EE217E146A00C96002 /* MXRecoveryKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32FFB4F1217E146A00C96002 /* MXRecoveryKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FFB4EF217E146A00C96002 /* MXRecoveryKey.m */; }; 3489B40A85709695DC4EB17C /* Pods_MatrixSDK_MatrixSDK_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F669A7EDB909643AE4F39F80 /* Pods_MatrixSDK_MatrixSDK_iOS.framework */; }; 3A0AF06B2705A11400679D1A /* MXSpaceGraphData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A0AF06A2705A11400679D1A /* MXSpaceGraphData.swift */; }; @@ -1055,7 +1055,7 @@ B14EF2AC2397E90400758AF0 /* MXMemoryStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 32D7767B1A27860600FC4AA2 /* MXMemoryStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF2AD2397E90400758AF0 /* MXEventAnnotationChunk.h in Headers */ = {isa = PBXBuildFile; fileRef = 327E9AD32284803000A98BC1 /* MXEventAnnotationChunk.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF2AF2397E90400758AF0 /* MXWellKnown.h in Headers */ = {isa = PBXBuildFile; fileRef = 323547D32226D3F500F15F94 /* MXWellKnown.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B14EF2B02397E90400758AF0 /* MXRecoveryKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FFB4EE217E146A00C96002 /* MXRecoveryKey.h */; }; + B14EF2B02397E90400758AF0 /* MXRecoveryKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FFB4EE217E146A00C96002 /* MXRecoveryKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; B14EF2B12397E90400758AF0 /* MXRealmAggregationsStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 32133013228AF4EF0070BA9B /* MXRealmAggregationsStore.h */; }; B14EF2B22397E90400758AF0 /* MXFileStoreMetaData.h in Headers */ = {isa = PBXBuildFile; fileRef = 32CE6FB61A409B1F00317F1E /* MXFileStoreMetaData.h */; }; B14EF2B32397E90400758AF0 /* MXRoomFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 32A31BC620D401FC005916C7 /* MXRoomFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1828,6 +1828,8 @@ ED35652D281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED35652B281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift */; }; ED35652F281153480002BF6A /* MXMegolmSessionDataUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED35652E281153480002BF6A /* MXMegolmSessionDataUnitTests.swift */; }; ED356530281153480002BF6A /* MXMegolmSessionDataUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED35652E281153480002BF6A /* MXMegolmSessionDataUnitTests.swift */; }; + ED36ED8628DD9E2200C86416 /* MXCryptoKeyBackupEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED36ED8528DD9E2100C86416 /* MXCryptoKeyBackupEngine.swift */; }; + ED36ED8728DD9E2200C86416 /* MXCryptoKeyBackupEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED36ED8528DD9E2100C86416 /* MXCryptoKeyBackupEngine.swift */; }; ED44F01128180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */; }; ED44F01228180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */; }; ED44F01428180EAB00452A5D /* MXSharedHistoryKeyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */; }; @@ -1836,6 +1838,10 @@ ED44F01B28180F4000452A5D /* MXSharedHistoryKeyManagerUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED44F01728180F1C00452A5D /* MXSharedHistoryKeyManagerUnitTests.swift */; }; ED47CB6D28523995004FD755 /* MXCryptoV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED47CB6C28523995004FD755 /* MXCryptoV2.swift */; }; ED47CB6E28523995004FD755 /* MXCryptoV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED47CB6C28523995004FD755 /* MXCryptoV2.swift */; }; + ED505DC028E1FD160079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED505DBD28E1FD130079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift */; }; + ED505DC128E1FD170079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED505DBD28E1FD130079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift */; }; + ED505DC428E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED505DC328E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift */; }; + ED505DC528E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED505DC328E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift */; }; ED51943928462D130006EEC6 /* MXRoomStateUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED51943828462D130006EEC6 /* MXRoomStateUnitTests.swift */; }; ED51943A28462D130006EEC6 /* MXRoomStateUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED51943828462D130006EEC6 /* MXRoomStateUnitTests.swift */; }; ED51943C284630090006EEC6 /* MXRestClientStub.m in Sources */ = {isa = PBXBuildFile; fileRef = ED51943B284630090006EEC6 /* MXRestClientStub.m */; }; @@ -1917,10 +1923,16 @@ ED8F1D352885B07500F897E7 /* MXCrossSigningInfoUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D1628857FE600F897E7 /* MXCrossSigningInfoUnitTests.swift */; }; ED8F1D3B2885BB2D00F897E7 /* MXCryptoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */; }; ED8F1D3C2885BB2D00F897E7 /* MXCryptoProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */; }; - EDAAC41C28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */ = {isa = PBXBuildFile; fileRef = EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EDAAC41D28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */ = {isa = PBXBuildFile; fileRef = EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; - EDAAC41F28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */; }; - EDAAC42028E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */; }; + EDAAC41928E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41828E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift */; }; + EDAAC41A28E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC41828E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift */; }; + EDAAC41C28E30F3C00DD89B5 /* (null) in Headers */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (Public, ); }; }; + EDAAC41D28E30F3C00DD89B5 /* (null) in Headers */ = {isa = PBXBuildFile; settings = {ATTRIBUTES = (Public, ); }; }; + EDAAC41F28E30F4C00DD89B5 /* (null) in Sources */ = {isa = PBXBuildFile; }; + EDAAC42028E30F4C00DD89B5 /* (null) in Sources */ = {isa = PBXBuildFile; }; + EDAAC42128E3174700DD89B5 /* MXCryptoSecretStore.h in Headers */ = {isa = PBXBuildFile; fileRef = EDAAC41228E2F86800DD89B5 /* MXCryptoSecretStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDAAC42228E3174700DD89B5 /* MXCryptoSecretStore.h in Headers */ = {isa = PBXBuildFile; fileRef = EDAAC41228E2F86800DD89B5 /* MXCryptoSecretStore.h */; settings = {ATTRIBUTES = (Public, ); }; }; + EDAAC42428E3177000DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC42328E3177000DD89B5 /* MXRecoveryServiceDependencies.swift */; }; + EDAAC42528E3177300DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDAAC42328E3177000DD89B5 /* MXRecoveryServiceDependencies.swift */; }; EDB4209227DF77390036AF39 /* MXEventsEnumeratorOnArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209027DF77310036AF39 /* MXEventsEnumeratorOnArrayTests.swift */; }; EDB4209327DF77390036AF39 /* MXEventsEnumeratorOnArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209027DF77310036AF39 /* MXEventsEnumeratorOnArrayTests.swift */; }; EDB4209527DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDB4209427DF822B0036AF39 /* MXEventsByTypesEnumeratorOnArrayTests.swift */; }; @@ -2969,10 +2981,13 @@ ED2DD11B286C4F3E00F06731 /* MXCryptoRequestsUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoRequestsUnitTests.swift; sourceTree = ""; }; ED35652B281150310002BF6A /* MXOlmInboundGroupSessionUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXOlmInboundGroupSessionUnitTests.swift; sourceTree = ""; }; ED35652E281153480002BF6A /* MXMegolmSessionDataUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXMegolmSessionDataUnitTests.swift; sourceTree = ""; }; + ED36ED8528DD9E2100C86416 /* MXCryptoKeyBackupEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoKeyBackupEngine.swift; sourceTree = ""; }; ED44F01028180BCC00452A5D /* MXSharedHistoryKeyRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyRequest.swift; sourceTree = ""; }; ED44F01328180EAB00452A5D /* MXSharedHistoryKeyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyManager.swift; sourceTree = ""; }; ED44F01728180F1C00452A5D /* MXSharedHistoryKeyManagerUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSharedHistoryKeyManagerUnitTests.swift; sourceTree = ""; }; ED47CB6C28523995004FD755 /* MXCryptoV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoV2.swift; sourceTree = ""; }; + ED505DBD28E1FD130079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoKeyBackupEngineUnitTests.swift; sourceTree = ""; }; + ED505DC328E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MXKeyBackupVersion+Stub.swift"; sourceTree = ""; }; ED51943828462D130006EEC6 /* MXRoomStateUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomStateUnitTests.swift; sourceTree = ""; }; ED51943B284630090006EEC6 /* MXRestClientStub.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRestClientStub.m; sourceTree = ""; }; ED51943E284630100006EEC6 /* MXRestClientStub.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRestClientStub.h; sourceTree = ""; }; @@ -3015,8 +3030,9 @@ ED8F1D312885AC5700F897E7 /* Device+Stub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Device+Stub.swift"; sourceTree = ""; }; ED8F1D332885ADE200F897E7 /* MXCryptoProtocolStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoProtocolStubs.swift; sourceTree = ""; }; ED8F1D3A2885BB2D00F897E7 /* MXCryptoProtocols.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXCryptoProtocols.swift; sourceTree = ""; }; - EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCryptoSecretStore.h; sourceTree = ""; }; - EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRecoveryServiceDependencies.swift; sourceTree = ""; }; + EDAAC41228E2F86800DD89B5 /* MXCryptoSecretStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXCryptoSecretStore.h; sourceTree = ""; }; + EDAAC41828E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXCryptoSecretStoreV2.swift; sourceTree = ""; }; + EDAAC42328E3177000DD89B5 /* MXRecoveryServiceDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRecoveryServiceDependencies.swift; sourceTree = ""; }; 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 = ""; }; @@ -3506,6 +3522,7 @@ 324DD297246AD2B500377005 /* MXSecretStorage.h */, 324DD2B0246BDC6800377005 /* MXSecretStorage_Private.h */, 324DD298246AD2B500377005 /* MXSecretStorage.m */, + EDAAC41828E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift */, ); path = SecretStorage; sourceTree = ""; @@ -3884,8 +3901,8 @@ 3284A59C1DB7C00600A09972 /* Store */ = { isa = PBXGroup; children = ( - EDAAC41B28E30F3C00DD89B5 /* MXCryptoSecretStore.h */, 3284A59D1DB7C00600A09972 /* MXCryptoStore.h */, + EDAAC41228E2F86800DD89B5 /* MXCryptoSecretStore.h */, 3259CD4C1DF85F6D00186944 /* MXRealmCryptoStore */, ); path = Store; @@ -4376,7 +4393,7 @@ isa = PBXGroup; children = ( 325AF3DE24897D2300EF937D /* Data */, - EDAAC41E28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift */, + EDAAC42328E3177000DD89B5 /* MXRecoveryServiceDependencies.swift */, 32F00AB92488E1CD00131741 /* MXRecoveryService.h */, 32F00ABA2488E1CD00131741 /* MXRecoveryService.m */, 32F00ABF2488FB3600131741 /* MXRecoveryService_Private.h */, @@ -5208,6 +5225,7 @@ ED8F1D292885A7DF00F897E7 /* Devices */, ED6DAC0F28C7889A00ECDCB6 /* RoomKeys */, ED44F01628180F1300452A5D /* KeySharing */, + ED505DBB28E1FCF50079A3D3 /* KeyBackup */, ED35652A281150230002BF6A /* Data */, ED21F67B28104BA1002FF83D /* Algorithms */, ED2DD11A286C4F3100F06731 /* CryptoMachine */, @@ -5275,6 +5293,31 @@ path = KeySharing; sourceTree = ""; }; + ED505DBB28E1FCF50079A3D3 /* KeyBackup */ = { + isa = PBXGroup; + children = ( + ED505DC228E206F70079A3D3 /* Data */, + ED505DBC28E1FCFC0079A3D3 /* Engine */, + ); + path = KeyBackup; + sourceTree = ""; + }; + ED505DBC28E1FCFC0079A3D3 /* Engine */ = { + isa = PBXGroup; + children = ( + ED505DBD28E1FD130079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift */, + ); + path = Engine; + sourceTree = ""; + }; + ED505DC228E206F70079A3D3 /* Data */ = { + isa = PBXGroup; + children = ( + ED505DC328E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift */, + ); + path = Data; + sourceTree = ""; + }; ED5C753428B3E80300D24E85 /* Logs */ = { isa = PBXGroup; children = ( @@ -5474,6 +5517,7 @@ EDE70DC728DA22F800099736 /* MXKeyBackupEngine.h */, EDD4197D28DCAA5F007F3757 /* MXNativeKeyBackupEngine.h */, EDD4198028DCAA7B007F3757 /* MXNativeKeyBackupEngine.m */, + ED36ED8528DD9E2100C86416 /* MXCryptoKeyBackupEngine.swift */, ); path = Engine; sourceTree = ""; @@ -5572,7 +5616,7 @@ B146D47421A5945800D8C2C6 /* MXAntivirusScanStatus.h in Headers */, 322691361E5EFF8700966A6E /* MXDeviceListOperationsPool.h in Headers */, 3281E8B719E42DFE00976E1A /* MXJSONModel.h in Headers */, - EDAAC41C28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */, + EDAAC41C28E30F3C00DD89B5 /* (null) in Headers */, B135066127E9CB6400BD3276 /* MXBeaconInfo.h in Headers */, EC5C562827A36EDB0014CBE9 /* MXInReplyTo.h in Headers */, EC8A539325B1BC77004E0802 /* MXCallSessionDescription.h in Headers */, @@ -5716,6 +5760,7 @@ B19A30C42404268600FB6F35 /* MXVerifyingAnotherUserQRCodeData.h in Headers */, B1C854EE25E7B497005867D0 /* MXRoomType.h in Headers */, B11BD45422CB583E0064D8B0 /* MXReplyEventBodyParts.h in Headers */, + EDAAC42128E3174700DD89B5 /* MXCryptoSecretStore.h in Headers */, 32549AF923F2E2790002576B /* MXKeyVerificationReady.h in Headers */, 32A151521DAF8A7200400192 /* MXQueuedEncryption.h in Headers */, 32A30B181FB4813400C8309E /* MXIncomingRoomKeyRequestManager.h in Headers */, @@ -6057,6 +6102,7 @@ B1EE98CD2804820800AB63F0 /* MXLocation.h in Headers */, B14EF2EA2397E90400758AF0 /* MXDeviceListOperation.h in Headers */, ECD623FF25D3DCC900DC0A0B /* MXOlmDecryption.h in Headers */, + EDAAC42228E3174700DD89B5 /* MXCryptoSecretStore.h in Headers */, B14EF2EB2397E90400758AF0 /* MXRoomAccountData.h in Headers */, B14EF2EC2397E90400758AF0 /* MXEventContentRelatesTo.h in Headers */, EC60EDFD265CFFD200B39A4E /* MXInvitedGroupSync.h in Headers */, @@ -6195,7 +6241,7 @@ 324AAC7E2399143400380A66 /* MXKeyVerificationCancel.h in Headers */, ED01915528C64E0400ED3A69 /* MXRoomKeyEventContent.h in Headers */, B14EF3372397E90400758AF0 /* MXRoomTombStoneContent.h in Headers */, - EDAAC41D28E30F3C00DD89B5 /* MXCryptoSecretStore.h in Headers */, + EDAAC41D28E30F3C00DD89B5 /* (null) in Headers */, 3274538B23FD918800438328 /* MXKeyVerificationByToDeviceRequest.h in Headers */, B14EF3382397E90400758AF0 /* MXFilterObject.h in Headers */, B14EF3392397E90400758AF0 /* MXRealmReactionCount.h in Headers */, @@ -6723,6 +6769,7 @@ 3293C701214BBA4F009B3DDB /* MXPeekingRoomSummary.m in Sources */, C602B58C1F2268F700B67D87 /* MXRoom.swift in Sources */, EC8A53D825B1BCC6004E0802 /* MXThirdPartyProtocolInstance.m in Sources */, + EDAAC42428E3177000DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */, 3A7509BB26FC61DF00B85773 /* MXSpaceNotificationCounter.swift in Sources */, 32E402BA21C957D2004E87A6 /* MXOlmSession.m in Sources */, EC8A53C525B1BC77004E0802 /* MXTurnServerResponse.m in Sources */, @@ -6768,6 +6815,7 @@ 3A59A49F25A7A16F00DDA1FC /* MXOlmOutboundGroupSession.m in Sources */, B146D4F221A5AF7F00D8C2C6 /* MXRealmEventScanMapper.m in Sources */, B11BD45A22CB58850064D8B0 /* MXReplyEventFormattedBodyParts.m in Sources */, + ED505DC428E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift in Sources */, B2EC7E2327F483DB00F40C26 /* MXEventAssetTypeMapper.swift in Sources */, 32CE6FB91A409B1F00317F1E /* MXFileStoreMetaData.m in Sources */, 3264DB921CEC528D00B99881 /* MXAccountData.m in Sources */, @@ -6838,6 +6886,7 @@ 32792BD52295A86600F4FC9D /* MXAggregatedReactionsUpdater.m in Sources */, 32618E7C20EFA45B00E1D2EA /* MXRoomMembers.m in Sources */, ED2DD118286C450600F06731 /* MXCryptoRequests.swift in Sources */, + EDAAC41928E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift in Sources */, 32B0E34123A378320054FF1A /* MXEventReference.m in Sources */, 324DD2A8246AE81300377005 /* MXSecretStorageKeyContent.m in Sources */, 02CAD43A217DD12F0074700B /* MXContentScanEncryptedBody.m in Sources */, @@ -6882,6 +6931,7 @@ EC0B94272718E64500B4D440 /* CoreDataContextable.swift in Sources */, 324BE4691E3FADB1008D99D4 /* MXMegolmExportEncryption.m in Sources */, 32A31BBF20D3F2EC005916C7 /* MXFilterObject.m in Sources */, + ED36ED8628DD9E2200C86416 /* MXCryptoKeyBackupEngine.swift in Sources */, 320DFDE719DD99B60068622A /* MXHTTPClient.m in Sources */, 32FE41371D0AB7070060835E /* MXEnumConstants.m in Sources */, EC60EDF4265CFFAC00B39A4E /* MXGroupsSyncResponse.m in Sources */, @@ -6929,7 +6979,7 @@ 324AAC73239913AD00380A66 /* MXKeyVerificationDone.m in Sources */, ED6DABFC28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */, B11556EE230C45C600B2A2CF /* MXIdentityServerRestClient.swift in Sources */, - EDAAC41F28E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */, + EDAAC41F28E30F4C00DD89B5 /* (null) in Sources */, 321CFDE722525A49004D31DF /* MXSASTransaction.m in Sources */, 32720D9D222EAA6F0086FFF5 /* MXDiscoveredClientConfig.m in Sources */, EC5C560C2798CEA00014CBE9 /* NSDictionary+MutableDeepCopy.m in Sources */, @@ -7127,6 +7177,7 @@ 18121F7A273E6E4200B68ADF /* PollBuilder.swift in Sources */, 18121F7F273E837300B68ADF /* PollModels.swift in Sources */, 32C03CB62123076F00D92712 /* DirectRoomTests.m in Sources */, + ED505DC128E1FD170079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift in Sources */, 329FB17C1A0A963700A5E88E /* MXRoomMemberTests.m in Sources */, ECE3DFA8270CF69500FB4C96 /* MockRoomSummary.swift in Sources */, EC1165CC27107F3E0089FA56 /* MXStoreRoomListDataManagerUnitTests.swift in Sources */, @@ -7338,6 +7389,7 @@ 3290293524CB10D000DA7BB6 /* MatrixSDKVersion.m in Sources */, 3A7509BC26FC61DF00B85773 /* MXSpaceNotificationCounter.swift in Sources */, B14EF21F2397E90400758AF0 /* MXMyUser.m in Sources */, + EDAAC42528E3177300DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */, EC60EDAB265CFE3B00B39A4E /* MXRoomSyncTimeline.m in Sources */, B14EF2202397E90400758AF0 /* (null) in Sources */, B14EF2212397E90400758AF0 /* MX3PID.swift in Sources */, @@ -7383,6 +7435,7 @@ B14EF2322397E90400758AF0 /* MXBugReportRestClient.m in Sources */, B14EF2342397E90400758AF0 /* MXCallKitAdapter.m in Sources */, 327C3E4E23A39D91006183D1 /* MXAggregatedReferencesUpdater.m in Sources */, + ED505DC528E206FC0079A3D3 /* MXKeyBackupVersion+Stub.swift in Sources */, B2EC7E2427F483DB00F40C26 /* MXEventAssetTypeMapper.swift in Sources */, B14EF2352397E90400758AF0 /* MXRoomPowerLevels.swift in Sources */, 32AF9287240EA2430008A0FD /* MXSecretShareRequest.m in Sources */, @@ -7453,6 +7506,7 @@ B14EF24F2397E90400758AF0 /* MXLRUCache.m in Sources */, B14EF2502397E90400758AF0 /* MXIncomingRoomKeyRequestManager.m in Sources */, ED2DD119286C450600F06731 /* MXCryptoRequests.swift in Sources */, + EDAAC41A28E2FCFE00DD89B5 /* MXCryptoSecretStoreV2.swift in Sources */, B14EF2512397E90400758AF0 /* MXRoomEventFilter.m in Sources */, B14EF2522397E90400758AF0 /* MXLoginPolicyData.m in Sources */, B19A30C72404268600FB6F35 /* MXQRCodeDataBuilder.m in Sources */, @@ -7497,6 +7551,7 @@ B14EF2622397E90400758AF0 /* MXAntivirusScanStatusFormatter.m in Sources */, EC0B94282718E64500B4D440 /* CoreDataContextable.swift in Sources */, 324AAC7C2399140D00380A66 /* MXKeyVerificationStart.m in Sources */, + ED36ED8728DD9E2200C86416 /* MXCryptoKeyBackupEngine.swift in Sources */, B14EF2632397E90400758AF0 /* MXReactionCountChange.m in Sources */, 324AAC762399140D00380A66 /* MXKeyVerificationCancel.m in Sources */, 32AF928D240EA3880008A0FD /* MXSecretShareSend.m in Sources */, @@ -7544,7 +7599,7 @@ B14EF2772397E90400758AF0 /* MXDecryptionResult.m in Sources */, ED6DABFD28C7542800ECDCB6 /* MXRoomKeyInfoFactory.swift in Sources */, B14EF2782397E90400758AF0 /* MXTransactionCancelCode.m in Sources */, - EDAAC42028E30F4C00DD89B5 /* MXRecoveryServiceDependencies.swift in Sources */, + EDAAC42028E30F4C00DD89B5 /* (null) in Sources */, B14EF2792397E90400758AF0 /* MXEventListener.m in Sources */, B1710B202613D01400A9B429 /* MXSpaceChildrenRequestParameters.swift in Sources */, B14EF27A2397E90400758AF0 /* MXSessionEventListener.swift in Sources */, @@ -7742,6 +7797,7 @@ 32C78BA8256D227D008130B1 /* MXCryptoMigrationTests.m in Sources */, 18121F7B273E6E4200B68ADF /* PollBuilder.swift in Sources */, 18121F80273E837400B68ADF /* PollModels.swift in Sources */, + ED505DC028E1FD160079A3D3 /* MXCryptoKeyBackupEngineUnitTests.swift in Sources */, B1E09A2F2397FD750057C069 /* MXErrorUnitTests.m in Sources */, B1660F1D260A20B900C3AA12 /* MXSpaceServiceTest.swift in Sources */, ECE3DFA9270CF69500FB4C96 /* MockRoomSummary.swift in Sources */, diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index 77b234c199..8789927d1c 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -43,7 +43,7 @@ class MXCryptoMachine { enum Error: Swift.Error { case invalidStorage case invalidEvent - case nothingToEncrypt + case cannotSerialize case missingRoom case missingVerificationContent case missingVerificationRequest @@ -60,6 +60,9 @@ class MXCryptoMachine { private let syncQueue = MXTaskQueue() private var roomQueues = RoomQueues() + private var hasUploadedKeys = false + private var keysUploadCallback: (() -> Void)? + private let log = MXNamedLog(name: "MXCryptoMachine") init(userId: String, deviceId: String, restClient: MXRestClient, getRoomAction: @escaping GetRoomAction) throws { @@ -76,6 +79,16 @@ class MXCryptoMachine { setLogger(logger: self) } + func onKeysUpload(callback: @escaping () -> Void) { + log.debug("->") + + if hasUploadedKeys { + callback() + } else { + keysUploadCallback = callback + } + } + private static func storeURL(for userId: String) throws -> URL { let container: URL if let sharedContainerURL = FileManager.default.applicationGroupContainerURL() { @@ -130,6 +143,8 @@ extension MXCryptoMachine: MXCryptoSyncing { deviceOneTimeKeysCounts: [String: NSNumber], unusedFallbackKeys: [String]? ) throws -> MXToDeviceSyncResponse { + log.debug("Recieving sync changes") + let events = toDevice?.jsonString() ?? "[]" let deviceChanges = DeviceLists( changed: deviceLists?.changed ?? [], @@ -174,6 +189,7 @@ extension MXCryptoMachine: MXCryptoSyncing { request: .init(body: body, deviceId: machine.deviceId()) ) try markRequestAsSent(requestId: requestId, requestType: .keysUpload, response: response.jsonString()) + broadcastKeysUploadIfNecessary() case .keysQuery(let requestId, let users): let response = try await requests.queryKeys(users: users) @@ -185,8 +201,11 @@ extension MXCryptoMachine: MXCryptoSyncing { ) try markRequestAsSent(requestId: requestId, requestType: .keysClaim, response: response.jsonString()) - case .keysBackup: - assertionFailure("Keys backup not implemented") + case .keysBackup(let requestId, let version, let rooms): + let response = try await requests.backupKeys( + request: .init(version: version, rooms: rooms) + ) + try markRequestAsSent(requestId: requestId, requestType: .keysBackup, response: MXTools.serialiseJSONObject(response)) case .roomMessage(let requestId, let roomId, let eventType, let content): guard let eventID = try await sendRoomMessage(roomId: roomId, eventType: eventType, content: content) else { @@ -239,6 +258,15 @@ extension MXCryptoMachine: MXCryptoSyncing { ) ) } + + private func broadcastKeysUploadIfNecessary() { + guard !hasUploadedKeys else { + return + } + hasUploadedKeys = true + keysUploadCallback?() + keysUploadCallback = nil + } } extension MXCryptoMachine: MXCryptoDevicesSource { @@ -302,7 +330,7 @@ extension MXCryptoMachine: MXCryptoEventEncrypting { func encrypt(_ content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] { guard let content = MXTools.serialiseJSONObject(content) else { - throw Error.nothingToEncrypt + throw Error.cannotSerialize } try await shareRoomKeysIfNecessary(roomId: roomId, users: users) @@ -319,6 +347,14 @@ extension MXCryptoMachine: MXCryptoEventEncrypting { return try MXEventDecryptionResult(event: result) } + func discardRoomKey(roomId: String) { + do { + try machine.discardRoomKey(roomId: roomId) + } catch { + log.error("Cannot discard room key", context: error) + } + } + // MARK: - Private private func updateTrackedUsers(users: [String]) async throws { @@ -510,6 +546,85 @@ extension MXCryptoMachine: MXCryptoSASVerifying { } } +extension MXCryptoMachine: MXCryptoBackup { + var isBackupEnabled: Bool { + return machine.backupEnabled() + } + + var backupKeys: BackupKeys? { + do { + return try machine.getBackupKeys() + } catch { + log.error("Failed fetching backup keys", context: error) + return nil + } + } + + var roomKeyCounts: RoomKeyCounts? { + do { + return try machine.roomKeyCounts() + } catch { + log.error("Cannot get room key counts", context: error) + return nil + } + } + + func enableBackup(key: MegolmV1BackupKey, version: String) throws { + try machine.enableBackupV1(key: key, version: version) + } + + func disableBackup() { + do { + try machine.disableBackup() + } catch { + log.error("Failed disabling backup", context: error) + } + } + + func saveRecoveryKey(key: BackupRecoveryKey, version: String?) throws { + try machine.saveRecoveryKey(key: key, version: version) + } + + func verifyBackup(version: MXKeyBackupVersion) -> Bool { + guard let string = version.jsonString() else { + log.error("Cannot serialize backup version") + return false + } + + do { + return try machine.verifyBackup(authData: string) + } catch { + log.error("Failed verifying backup", context: error) + return false + } + } + + func sign(object: [AnyHashable: Any]) throws -> [String: [String: String]] { + guard let message = MXCryptoTools.canonicalJSONString(forJSON: object) else { + throw Error.cannotSerialize + } + return machine.sign(message: message) + } + + func backupRoomKeys() async throws { + guard + let request = try machine.backupRoomKeys(), + case .keysBackup = request + else { + return + } + try await handleRequest(request) + } + + func importDecryptedKeys(roomKeys: [MXMegolmSessionData], progressListener: ProgressListener) throws -> KeysImportResult { + let jsonKeys = roomKeys.compactMap { $0.jsonDictionary() } + guard let json = MXTools.serialiseJSONObject(jsonKeys) else { + throw Error.cannotSerialize + } + return try machine.importDecryptedKeys(keys: json, progressListener: progressListener) + } +} + extension MXCryptoMachine: Logger { func log(logLine: String) { MXLog.debug("[MXCryptoMachine] \(logLine)") diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift index b7807996d2..df92892587 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift @@ -60,6 +60,7 @@ protocol MXCryptoEventEncrypting: MXCryptoIdentity { func shareRoomKeysIfNecessary(roomId: String, users: [String]) async throws func encrypt(_ content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] func decryptEvent(_ event: MXEvent) throws -> MXEventDecryptionResult + func discardRoomKey(roomId: String) } /// Cross-signing functionality @@ -91,4 +92,21 @@ protocol MXCryptoSASVerifying: MXCryptoVerifying { func emojiIndexes(sas: Sas) throws -> [Int] } +/// Room keys backup functionality +protocol MXCryptoBackup { + var isBackupEnabled: Bool { get } + var backupKeys: BackupKeys? { get } + var roomKeyCounts: RoomKeyCounts? { get } + + func enableBackup(key: MegolmV1BackupKey, version: String) throws + func disableBackup() + func saveRecoveryKey(key: BackupRecoveryKey, version: String?) throws + + func verifyBackup(version: MXKeyBackupVersion) -> Bool + func sign(object: [AnyHashable: Any]) throws -> [String: [String: String]] + + func backupRoomKeys() async throws + func importDecryptedKeys(roomKeys: [MXMegolmSessionData], progressListener: ProgressListener) throws -> KeysImportResult +} + #endif diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift index 2427eb43ea..031e6d6832 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoRequests.swift @@ -105,6 +105,20 @@ struct MXCryptoRequests { ) } } + + func backupKeys(request: KeysBackupRequest) async throws -> [AnyHashable: Any] { + return try await performCallbackRequest { continuation in + restClient.sendKeysBackup( + request.keysBackupData, + version: request.version, + success: { + continuation(.success($0 ?? [:])) + }, failure: { + continuation(.failure($0 ?? Error.unknownError)) + } + ) + } + } } /// Convenience structs mapping Rust requests to data for native REST API requests @@ -174,6 +188,22 @@ extension MXCryptoRequests { self.content = json } } + + struct KeysBackupRequest { + let version: String + let keysBackupData: MXKeysBackupData + + init(version: String, rooms: String) throws { + self.version = version + guard + let json = MXTools.deserialiseJSONString(rooms), + let data = MXKeysBackupData(fromJSON: ["rooms": json]) + else { + throw Error.cannotCreateRequest + } + self.keysBackupData = data + } + } } extension UploadSigningKeysRequest { diff --git a/MatrixSDK/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngine.swift b/MatrixSDK/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngine.swift new file mode 100644 index 0000000000..8f98d0c839 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngine.swift @@ -0,0 +1,389 @@ +// +// 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 + +#if DEBUG && os(iOS) + +import MatrixSDKCrypto + +class MXCryptoKeyBackupEngine: NSObject, MXKeyBackupEngine { + enum Error: Swift.Error { + case unknownBackupVersion + case invalidData + case invalidPrivateKey + case algorithmNotSupported(String) + } + + var enabled: Bool { + return backup.isBackupEnabled + } + + var version: String? { + return backup.backupKeys?.backupVersion() + } + + private var recoveryKey: BackupRecoveryKey? { + return backup.backupKeys?.recoveryKey() + } + + private let backup: MXCryptoBackup + private let log = MXNamedLog(name: "MXCryptoKeyBackupEngine") + + init(backup: MXCryptoBackup) { + self.backup = backup + } + + // MARK: - Enable / Disable engine + + func enableBackup(with keyBackupversion: MXKeyBackupVersion) throws { + log.debug("->") + + guard let version = keyBackupversion.version else { + log.error("Unknown backup version") + throw Error.unknownBackupVersion + } + + let key = try MegolmV1BackupKey(keyBackupVersion: keyBackupversion) + try backup.enableBackup(key: key, version: version) + log.debug("Backup enabled") + } + + func disableBackup() { + log.debug("->") + backup.disableBackup() + } + + // MARK: - Private / Recovery key management + + func privateKey() -> Data? { + guard let recoveryKey = recoveryKey?.toBase58() else { + log.debug("No known backup key") + return nil + } + do { + return try MXRecoveryKey.decode(recoveryKey) + } catch { + log.error("Cannot create private key from recovery key", context: error) + return nil + } + } + + func savePrivateKey(_ privateKey: Data, version: String) { + let recoveryKey = MXRecoveryKey.encode(privateKey) + do { + let key = try BackupRecoveryKey.fromBase58(key: recoveryKey) + try backup.saveRecoveryKey(key: key, version: version) + log.debug("New private key saved") + } catch { + log.error("Cannot save private key", context: error) + } + } + + func hasValidPrivateKey() -> Bool { + return recoveryKey != nil + } + + func hasValidPrivateKey(for keyBackupVersion: MXKeyBackupVersion) -> Bool { + guard let recoveryKey = recoveryKey else { + log.debug("Not recovery key") + return false + } + return recoveryKey.megolmV1PublicKey().publicKey == publicKey(for: keyBackupVersion) + } + + func validPrivateKey(forRecoveryKey recoveryKey: String, for keyBackupVersion: MXKeyBackupVersion) throws -> Data { + let key = try BackupRecoveryKey.fromBase58(key: recoveryKey) + guard key.megolmV1PublicKey().publicKey == publicKey(for: keyBackupVersion) else { + throw Error.invalidPrivateKey + } + + let privateKey = try MXRecoveryKey.decode(recoveryKey) + log.debug("Created valid private key from recovery key") + return privateKey + } + + func recoveryKey(fromPassword password: String, in keyBackupVersion: MXKeyBackupVersion) throws -> String { + guard + let authData = MXCurve25519BackupAuthData(fromJSON: keyBackupVersion.authData), + let salt = authData.privateKeySalt + else { + log.error("Invalid auth data") + throw Error.invalidData + } + + let key = BackupRecoveryKey.fromPassphrase( + passphrase: password, + salt: salt, + rounds: Int32(authData.privateKeyIterations) + ) + + log.debug("Created recovery key from password") + return key.toBase58() + } + + // MARK: - Backup versions + + func prepareKeyBackupVersion( + withPassword password: String?, + algorithm: String?, + success: @escaping (MXMegolmBackupCreationInfo) -> Void, + failure: @escaping (Swift.Error) -> Void + ) { + log.debug("->") + + guard algorithm == nil || algorithm == kMXCryptoCurve25519KeyBackupAlgorithm else { + log.error("Algorithm not supported") + failure(Error.algorithmNotSupported(algorithm!)) + return + } + + let key = password != nil ? BackupRecoveryKey.newFromPassphrase(passphrase: password!) : BackupRecoveryKey() + let publicKey = key.megolmV1PublicKey() + + let authData = MXCurve25519BackupAuthData() + authData.publicKey = publicKey.publicKey + if let info = publicKey.passphraseInfo { + authData.privateKeySalt = info.privateKeySalt + authData.privateKeyIterations = UInt(info.privateKeyIterations) + } + + do { + authData.signatures = try backup.sign(object: authData.signalableJSONDictionary) + } catch { + log.error("Cannot create signatures", context: error) + } + + let info = MXMegolmBackupCreationInfo() + info.algorithm = publicKey.backupAlgorithm + info.authData = authData + info.recoveryKey = key.toBase58() + + log.debug("Key backup version info is ready") + success(info) + } + + func trust(for keyBackupVersion: MXKeyBackupVersion) -> MXKeyBackupVersionTrust { + let trust = MXKeyBackupVersionTrust() + trust.usable = backup.verifyBackup(version: keyBackupVersion) + log.debug("Key backup version is \(trust.usable ? "trusted" : "untrusted")") + return trust + } + + func authData(from keyBackupVersion: MXKeyBackupVersion) throws -> MXBaseKeyBackupAuthData { + guard let data = MXCurve25519BackupAuthData(fromJSON: keyBackupVersion.authData) else { + throw Error.invalidData + } + return data + } + + func signObject(_ object: [AnyHashable : Any]) -> [AnyHashable : Any] { + do { + return try backup.sign(object: object) + } catch { + log.error("Failed signing object", context: error) + return [:] + } + } + + // MARK: - Backup keys + + func hasKeysToBackup() -> Bool { + guard let counts = backup.roomKeyCounts else { + return false + } + return counts.total > counts.backedUp + } + + func backupProgress() -> Progress { + guard let counts = backup.roomKeyCounts else { + return Progress() + } + + let progress = Progress(totalUnitCount: counts.total) + progress.completedUnitCount = counts.backedUp + + log.debug("Backed up \(progress.completedUnitCount) out of \(progress.totalUnitCount) keys") + return progress + } + + func backupKeys( + success: @escaping () -> Void, + failure: @escaping (Swift.Error) -> Void + ) { + log.debug("->") + Task { + do { + try await backup.backupRoomKeys() + await MainActor.run { + log.debug("Successfully backed up keys") + success() + } + } catch { + await MainActor.run { + log.error("Failed backing up keys", context: error) + failure(error) + } + } + } + } + + func importKeys( + with keysBackupData: MXKeysBackupData, + privateKey: Data, + keyBackupVersion: MXKeyBackupVersion, + success: @escaping (UInt, UInt) -> Void, + failure: @escaping (Swift.Error) -> Void + ) { + let count = keysBackupData.rooms + .map { roomId, room in + room.sessions.count + } + .reduce(0, +) + + log.debug("Importing \(count) encrypted sessions") + + let sessions = keysBackupData.rooms + .flatMap { roomId, room in + room.sessions + .compactMap { sessionId, keyBackup in + decrypt( + keyBackupData:keyBackup, + keyBackupVersion: keyBackupVersion, + privateKey: privateKey, + forSession: sessionId, + inRoom: roomId + ) + } + } + + log.debug("Decrypted \(sessions.count) sessions") + + do { + let result = try backup.importDecryptedKeys(roomKeys: sessions, progressListener: self) + log.debug("Successfully imported \(result.imported) out of \(result.total) sessions") + success(UInt(result.total), UInt(result.imported)) + } catch { + log.error("Failed importing sessions", context: error) + failure(error) + } + } + + private func decrypt( + keyBackupData: MXKeyBackupData, + keyBackupVersion: MXKeyBackupVersion, + privateKey: Data, + forSession sessionId: String, + inRoom roomId: String + ) -> MXMegolmSessionData? { + log.debug("->") + + let recoveryKey: BackupRecoveryKey + do { + let key = MXRecoveryKey.encode(privateKey) + recoveryKey = try BackupRecoveryKey.fromBase58(key: key) + } catch { + log.error("Failed creating recovery key") + return nil + } + + guard + let ciphertext = keyBackupData.sessionData["ciphertext"] as? String, + let mac = keyBackupData.sessionData["mac"] as? String, + let ephemeral = keyBackupData.sessionData["ephemeral"] as? String + else { + log.error("Missing session data properties") + return nil + } + + let plaintext: String + do { + plaintext = try recoveryKey.decryptV1( + ephemeralKey: ephemeral, + mac: mac, + ciphertext: ciphertext + ) + } catch { + log.error("Failed decrypting backup data", context: error) + return nil + } + + guard + let json = MXTools.deserialiseJSONString(plaintext) as? [AnyHashable: Any], + let data = MXMegolmSessionData(fromJSON: json) + else { + log.error("Failed serializing data") + return nil + } + + data.sessionId = sessionId + data.roomId = roomId + data.isUntrusted = true // Asymmetric backups are untrusted by default + + log.debug("Decrypted key backup data") + return data + } + + // MARK: - Private + + func publicKey(for keyBackupVersion: MXKeyBackupVersion) -> String? { + guard let authData = MXCurve25519BackupAuthData(fromJSON: keyBackupVersion.authData) else { + log.error("Cannot create auth data for backup version") + return nil + } + return authData.publicKey + } +} + +extension MXCryptoKeyBackupEngine: ProgressListener { + func onProgress(progress: Int32, total: Int32) { + log.debug("Backup progress \(progress) of \(total) total") + } +} + +extension MegolmV1BackupKey { + enum Error: Swift.Error { + case invalidData + } + + init(keyBackupVersion: MXKeyBackupVersion) throws { + guard + let authData = MXCurve25519BackupAuthData(fromJSON: keyBackupVersion.authData), + let signatures = authData.signatures as? [String: [String: String]] + else { + throw Error.invalidData + } + + let info: PassphraseInfo? + if let salt = authData.privateKeySalt { + info = PassphraseInfo( + privateKeySalt: salt, + privateKeyIterations: Int32(authData.privateKeyIterations) + ) + } else { + info = nil + } + + self.init( + publicKey: authData.publicKey, + signatures: signatures, + passphraseInfo: info, + backupAlgorithm: keyBackupVersion.algorithm + ) + } +} + +#endif diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h index b79b041e46..3cfbd7484b 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h @@ -19,8 +19,9 @@ #import "MXRestClient.h" #import "MXMegolmBackupCreationInfo.h" #import "MXKeyBackupVersionTrust.h" +#import "MXSecretShareManager.h" -@protocol MXKeyBackupAlgorithm; +@protocol MXKeyBackupAlgorithm, MXKeyBackupEngine; NS_ASSUME_NONNULL_BEGIN @@ -113,6 +114,19 @@ FOUNDATION_EXPORT NSString *const kMXKeyBackupDidStateChangeNotification; */ @interface MXKeyBackup : NSObject +/** + Constructor. + + @param engine backup engine that stores and manages keys + @param restClient rest client to perform http requests + @param secretShareManager manages of secrets sharing + @param queue dispatch queue to perform all operations on + */ +- (instancetype)initWithEngine:(id)engine + restClient:(MXRestClient *)restClient + secretShareManager:(MXSecretShareManager *)secretShareManager + queue:(dispatch_queue_t)queue; + #pragma mark - Backup management /** @@ -397,6 +411,19 @@ FOUNDATION_EXPORT NSString *const kMXKeyBackupDidStateChangeNotification; */ @property (nonatomic, readonly) BOOL canBeRefreshed; +/** + Check the server for an active key backup. + + If one is present and has a valid signature from one of the user's verified + devices, start backing up to it. + */ +- (void)checkAndStartKeyBackup; + +/** + Do a backup if there are new keys. + */ +- (void)maybeSendKeyBackup; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h index f7931cd265..4b980498c7 100644 --- a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h @@ -27,37 +27,11 @@ NS_ASSUME_NONNULL_BEGIN */ @interface MXKeyBackup () -/** - Constructor. - - @param engine backup engine that stores and manages keys - @param restClient rest client to perform http requests - @param secretShareManager manages of secrets sharing - @param queue dispatch queue to perform all operations on - */ -- (instancetype)initWithEngine:(id)engine - restClient:(MXRestClient *)restClient - secretShareManager:(MXSecretShareManager *)secretShareManager - queue:(dispatch_queue_t)queue; - -/** - Check the server for an active key backup. - - If one is present and has a valid signature from one of the user's verified - devices, start backing up to it. - */ -- (void)checkAndStartKeyBackup; - /** * Reset all local key backup data. */ - (void)resetKeyBackupData; -/** - Do a backup if there are new keys. - */ -- (void)maybeSendKeyBackup; - - (void)requestPrivateKeys:(void (^)(void))onComplete; - (BOOL)isSecretValid:(NSString*)secret forKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion; diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index 842ef8079a..e0f9083138 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -87,8 +87,7 @@ private class MXCryptoV2: MXCrypto { } public override var backup: MXKeyBackup! { - log.debug("Not implemented") - return MXKeyBackup() + return keyBackup } public override var keyVerificationManager: MXKeyVerificationManager! { @@ -96,18 +95,15 @@ private class MXCryptoV2: MXCrypto { } public override var recoveryService: MXRecoveryService! { - log.debug("Not implemented") - return MXRecoveryService() + return recovery } public override var secretStorage: MXSecretStorage! { - log.debug("Not implemented") - return MXSecretStorage() + return secretsStorage } public override var secretShareManager: MXSecretShareManager! { - log.debug("Not implemented") - return MXSecretShareManager() + return secretsManager } public override var crossSigning: MXCrossSigning! { @@ -115,20 +111,27 @@ private class MXCryptoV2: MXCrypto { } private let userId: String + private let cryptoQueue: DispatchQueue + private weak var session: MXSession? private let machine: MXCryptoMachine private let deviceInfoSource: MXDeviceInfoSource private let crossSigningInfoSource: MXCrossSigningInfoSource private let trustLevelSource: MXTrustLevelSource - private let crossSign: MXCrossSigningV2 private let keyVerification: MXKeyVerificationManagerV2 + private let secretsStorage: MXSecretStorage + private let secretsManager: MXSecretShareManager + private let backupEngine: MXCryptoKeyBackupEngine + private let keyBackup: MXKeyBackup + private var recovery: MXRecoveryService private let log = MXNamedLog(name: "MXCryptoV2") public init(userId: String, deviceId: String, session: MXSession, restClient: MXRestClient) throws { self.userId = userId + self.cryptoQueue = DispatchQueue(label: "MXCryptoV2-\(userId)") self.session = session machine = try MXCryptoMachine( @@ -139,6 +142,7 @@ private class MXCryptoV2: MXCrypto { session?.room(withRoomId: roomId) } ) + deviceInfoSource = MXDeviceInfoSource(source: machine) crossSigningInfoSource = MXCrossSigningInfoSource(source: machine) trustLevelSource = MXTrustLevelSource( @@ -162,6 +166,32 @@ private class MXCryptoV2: MXCrypto { } ) + secretsManager = MXSecretShareManager() + secretsStorage = MXSecretStorage(matrixSession: session, processingQueue: cryptoQueue) + + backupEngine = MXCryptoKeyBackupEngine(backup: machine) + keyBackup = MXKeyBackup( + engine: backupEngine, + restClient: restClient, + secretShareManager: secretsManager, + queue: cryptoQueue + ) + + recovery = MXRecoveryService( + dependencies: .init( + credentials: restClient.credentials, + backup: keyBackup, + secretStorage: secretsStorage, + secretStore: MXCryptoSecretStoreV2( + backup: keyBackup, + backupEngine: backupEngine + ), + crossSigning: crossSign, + cryptoQueue: cryptoQueue + ), + delegate: keyVerification + ) + super.init() } @@ -184,9 +214,17 @@ private class MXCryptoV2: MXCrypto { // MARK: - Start / close - public override func start(_ onComplete: (() -> Void)!, failure: ((Swift.Error?) -> Void)!) { + public override func start( + _ onComplete: (() -> Void)!, + failure: ((Swift.Error?) -> Void)! + ) { onComplete?() - log.debug("Not implemented") + machine.onKeysUpload { [weak self] in + guard let self = self else { return } + + self.crossSign.refreshState(success: nil) + self.keyBackup.checkAndStart() + } } public override func close(_ deleteStore: Bool) { @@ -301,7 +339,14 @@ private class MXCryptoV2: MXCrypto { } public override func discardOutboundGroupSessionForRoom(withRoomId roomId: String!, onComplete: (() -> Void)!) { - log.debug("Not implemented") + guard let roomId = roomId else { + log.failure("Missing room id") + return + } + + log.debug("Discarding room key") + machine.discardRoomKey(roomId: roomId) + onComplete?() } // MARK: - Sync @@ -320,6 +365,7 @@ private class MXCryptoV2: MXCrypto { unusedFallbackKeys: syncResponse.unusedFallbackKeys ) keyVerification.handleDeviceEvents(toDevice.events) + backup.maybeSend() } catch { log.error("Cannot handle sync", context: error) } diff --git a/MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift b/MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift new file mode 100644 index 0000000000..f56a62f20a --- /dev/null +++ b/MatrixSDK/Crypto/SecretStorage/MXCryptoSecretStoreV2.swift @@ -0,0 +1,62 @@ +// +// 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 + +/// Secret store compatible with Rust-based Crypto V2, where +/// backup secrets are stored internally in the Crypto machine +/// and others have to be managed manually. +class MXCryptoSecretStoreV2: NSObject, MXCryptoSecretStore { + + private let backup: MXKeyBackup + private let backupEngine: MXKeyBackupEngine + private let log = MXNamedLog(name: "MXCryptoSecretStoreV2") + + init(backup: MXKeyBackup, backupEngine: MXKeyBackupEngine) { + self.backup = backup + self.backupEngine = backupEngine + } + + func storeSecret(_ secret: String!, withSecretId secretId: String!) { + guard let version = backup.keyBackupVersion?.version else { + log.error("No key backup version available") + return + } + + if secretId == MXSecretId.keyBackup.takeUnretainedValue() as String { + let privateKey = MXBase64Tools.data(fromBase64: secret) + backupEngine.savePrivateKey(privateKey, version: version) + } else { + log.error("Not implemented") + } + } + + func secret(withSecretId secretId: String!) -> String! { + if secretId == MXSecretId.keyBackup.takeUnretainedValue() as String { + guard let privateKey = backupEngine.privateKey() else { + return nil + } + return MXBase64Tools.base64(from: privateKey) + } else { + log.error("Not implemented") + return nil + } + } + + func deleteSecret(withSecretId secretId: String!) { + log.error("Not implemented") + } +} diff --git a/MatrixSDK/Crypto/SecretStorage/MXSecretStorage.h b/MatrixSDK/Crypto/SecretStorage/MXSecretStorage.h index c0c4fde43e..516426dd1c 100644 --- a/MatrixSDK/Crypto/SecretStorage/MXSecretStorage.h +++ b/MatrixSDK/Crypto/SecretStorage/MXSecretStorage.h @@ -21,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN +@class MXSession; #pragma mark - Constants @@ -45,6 +46,13 @@ typedef NS_ENUM(NSUInteger, MXSecretStorageErrorCode) */ @interface MXSecretStorage : NSObject +/** + Constructor. + + @param mxSession the related 'MXSession' instance. + */ +- (instancetype)initWithMatrixSession:(MXSession *)mxSession processingQueue:(dispatch_queue_t)processingQueue; + #pragma mark - Secret Storage Key /** diff --git a/MatrixSDK/Crypto/SecretStorage/MXSecretStorage_Private.h b/MatrixSDK/Crypto/SecretStorage/MXSecretStorage_Private.h index 8e565a6c87..1163ce3194 100644 --- a/MatrixSDK/Crypto/SecretStorage/MXSecretStorage_Private.h +++ b/MatrixSDK/Crypto/SecretStorage/MXSecretStorage_Private.h @@ -23,13 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @interface MXSecretStorage () -/** - Constructor. - - @param mxSession the related 'MXSession' instance. - */ -- (instancetype)initWithMatrixSession:(MXSession *)mxSession processingQueue:(dispatch_queue_t)processingQueue; - - (nullable MXEncryptedSecretContent *)encryptedZeroStringWithPrivateKey:(NSData*)privateKey iv:(nullable NSData*)iv error:(NSError**)error; - (nullable MXEncryptedSecretContent *)encryptSecret:(NSString*)unpaddedBase64Secret withSecretId:(nullable NSString*)secretId privateKey:(NSData*)privateKey iv:(nullable NSData*)iv error:(NSError**)error; diff --git a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift index 3d851acd57..65b1bfd75b 100644 --- a/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift +++ b/MatrixSDK/Crypto/Verification/MXKeyVerificationManagerV2.swift @@ -301,4 +301,10 @@ class MXKeyVerificationManagerV2: MXKeyVerificationManager { } } +extension MXKeyVerificationManagerV2: MXRecoveryServiceDelegate { + func setUserVerification(_ isTrusted: Bool, forUser: String, success: () -> Void, failure: (Swift.Error) -> Void) { + log.error("Not implemented") + } +} + #endif diff --git a/MatrixSDK/MatrixSDK.h b/MatrixSDK/MatrixSDK.h index c77f468c48..67b331af2f 100644 --- a/MatrixSDK/MatrixSDK.h +++ b/MatrixSDK/MatrixSDK.h @@ -173,6 +173,9 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXSharedHistoryKeyService.h" #import "MXRoomKeyEventContent.h" #import "MXForwardedRoomKeyEventContent.h" +#import "MXKeyBackupEngine.h" +#import "MXCryptoTools.h" +#import "MXRecoveryKey.h" // Sync response models #import "MXSyncResponse.h" @@ -197,3 +200,4 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXBeacon.h" #import "MXEventAssetType.h" #import "MXDevice.h" + diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift index 87cbd406e2..c56dcb2810 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift @@ -19,7 +19,7 @@ import Foundation #if DEBUG && os(iOS) -import MatrixSDKCrypto +@testable import MatrixSDKCrypto class CryptoIdentityStub: MXCryptoIdentity { var userId: String = "Alice" @@ -144,4 +144,44 @@ extension CryptoVerificationStub: MXCryptoSASVerifying { } } +class CryptoBackupStub: MXCryptoBackup { + var isBackupEnabled: Bool = false + var backupKeys: BackupKeys? + var roomKeyCounts: RoomKeyCounts? + + var versionSpy: String? + var backupKeySpy: MegolmV1BackupKey? + var recoveryKeySpy: BackupRecoveryKey? + var roomKeysSpy: [MXMegolmSessionData]? + + func enableBackup(key: MegolmV1BackupKey, version: String) throws { + versionSpy = version + backupKeySpy = key + } + + func disableBackup() { + } + + func saveRecoveryKey(key: BackupRecoveryKey, version: String?) throws { + recoveryKeySpy = key + } + + func verifyBackup(version: MXKeyBackupVersion) -> Bool { + return true + } + + var stubbedSignature = [String : [String : String]]() + func sign(object: [AnyHashable : Any]) throws -> [String : [String : String]] { + return stubbedSignature + } + + func backupRoomKeys() async throws { + } + + func importDecryptedKeys(roomKeys: [MXMegolmSessionData], progressListener: ProgressListener) throws -> KeysImportResult { + roomKeysSpy = roomKeys + return KeysImportResult(imported: Int64(roomKeys.count), total: Int64(roomKeys.count), keys: [:]) + } +} + #endif diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift index 82922ae3f5..0ac72c0519 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoRequestsUnitTests.swift @@ -85,6 +85,31 @@ class MXCryptoRequestsUnitTests: XCTestCase { XCTAssertEqual(request.devices.map as? [String: [String: String]], keys) } + func test_canCreateKeysBackupRequest() { + let rooms: [String: Any] = [ + "A": [ + "sessions": [ + "1": [ + "first_message_index": 1, + "forwarded_count": 0, + "is_verified": true, + ], + ] + ], + ] + let string = MXTools.serialiseJSONObject(rooms) + + do { + let request = try MXCryptoRequests.KeysBackupRequest(version: "2", rooms: string ?? "") + XCTAssertEqual(request.version, "2") + XCTAssertEqual(request.keysBackupData.jsonDictionary() as NSDictionary, [ + "rooms": rooms + ]) + } catch { + XCTFail("Failed creating keys backup request with error - \(error)") + } + } + func test_uploadSignatureKeysRequest_canGetJsonKeys() throws { let request = UploadSigningKeysRequest( masterKey: MXTools.serialiseJSONObject(["key": "A"]), diff --git a/MatrixSDKTests/Crypto/KeyBackup/Data/MXKeyBackupVersion+Stub.swift b/MatrixSDKTests/Crypto/KeyBackup/Data/MXKeyBackupVersion+Stub.swift new file mode 100644 index 0000000000..c378620c49 --- /dev/null +++ b/MatrixSDKTests/Crypto/KeyBackup/Data/MXKeyBackupVersion+Stub.swift @@ -0,0 +1,39 @@ +// +// 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 + +extension MXKeyBackupVersion { + static func stub( + version: String = "3", + publicKey: String = "ABC" + ) -> MXKeyBackupVersion { + return MXKeyBackupVersion(fromJSON: [ + "version": version, + "algorithm": kMXCryptoCurve25519KeyBackupAlgorithm, + "auth_data": [ + "public_key": publicKey, + "private_key_salt": "salt", + "private_key_iterations": 10, + "signatures": [ + "bob": [ + "ABC": "XYZ" + ] + ] + ] + ])! + } +} diff --git a/MatrixSDKTests/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngineUnitTests.swift b/MatrixSDKTests/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngineUnitTests.swift new file mode 100644 index 0000000000..65f6b3d796 --- /dev/null +++ b/MatrixSDKTests/Crypto/KeyBackup/Engine/MXCryptoKeyBackupEngineUnitTests.swift @@ -0,0 +1,214 @@ +// +// 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 +@testable import MatrixSDK + +#if DEBUG && os(iOS) + +import MatrixSDKCrypto + +class MXCryptoKeyBackupEngineUnitTests: XCTestCase { + var backup: CryptoBackupStub! + var engine: MXCryptoKeyBackupEngine! + + override func setUp() { + backup = CryptoBackupStub() + engine = MXCryptoKeyBackupEngine(backup: backup) + } + + func test_createsBackupKeyFromVersion() { + let version = MXKeyBackupVersion.stub() + + do { + try engine.enableBackup(with: version) + + XCTAssertEqual(backup.versionSpy, "3") + XCTAssertEqual(backup.backupKeySpy?.publicKey, "ABC") + XCTAssertEqual(backup.backupKeySpy?.passphraseInfo?.privateKeySalt, "salt") + XCTAssertEqual(backup.backupKeySpy?.passphraseInfo?.privateKeyIterations, 10) + XCTAssertEqual(backup.backupKeySpy?.signatures, [ + "bob": [ + "ABC": "XYZ" + ] + ]) + } catch { + XCTFail("Failed enabling backup - \(error)") + } + } + + func test_hasKeysToBackup() { + backup.roomKeyCounts = .init(total: 0, backedUp: 0) + XCTAssertFalse(engine.hasKeysToBackup()) + + backup.roomKeyCounts = .init(total: 1, backedUp: 0) + XCTAssertTrue(engine.hasKeysToBackup()) + + backup.roomKeyCounts = .init(total: 2, backedUp: 3) + XCTAssertFalse(engine.hasKeysToBackup()) + } + + func test_validPrivateKeyFromRecoveryKey_failsForInvalidPublicKey() { + let key = BackupRecoveryKey() + let invalidVersion = MXKeyBackupVersion.stub( + publicKey: "invalid_key" + ) + + do { + _ = try engine.validPrivateKey(forRecoveryKey: key.toBase58(), for: invalidVersion) + XCTFail("Should not succeed") + } catch MXCryptoKeyBackupEngine.Error.invalidPrivateKey { + XCTAssert(true) + } catch { + XCTFail("Unknown error \(error)") + } + } + + func test_validPrivateKeyFromRecoveryKey_succeedsForInvalidPublicKey() { + let key = BackupRecoveryKey() + let invalidVersion = MXKeyBackupVersion.stub( + publicKey: key.megolmV1PublicKey().publicKey + ) + + do { + let privateKey = try engine.validPrivateKey(forRecoveryKey: key.toBase58(), for: invalidVersion) + XCTAssertEqual(privateKey, try! MXRecoveryKey.decode(key.toBase58())) + } catch { + XCTFail("Unknown error \(error)") + } + } + + func test_prepareKeyBackupVersion() { + backup.stubbedSignature = [ + "Bob": [ + "ed25519": "123" + ] + ] + + let exp = expectation(description: "exp") + engine.prepareKeyBackupVersion( + withPassword: "pass", + algorithm: nil, + success: { info in + + let privateKey = try! MXRecoveryKey.decode(info.recoveryKey) + let publicKey = OLMPkDecryption().setPrivateKey(privateKey, error: nil) + + let authData = info.authData as? MXCurve25519BackupAuthData + XCTAssertEqual(info.algorithm, kMXCryptoCurve25519KeyBackupAlgorithm) + XCTAssertEqual(authData?.publicKey, publicKey) + XCTAssertEqual(authData?.signatures as? NSDictionary, [ + "Bob": [ + "ed25519": "123" + ] + ]) + + exp.fulfill() + }, + failure: { + exp.fulfill() + XCTFail("Unknown error \($0)") + } + ) + + waitForExpectations(timeout: 1) + } + + func test_importKeys() { + // Prepare private and public key for encryption + let key = BackupRecoveryKey() + let privateKey = try! MXRecoveryKey.decode(key.toBase58()) + let publicKey = OLMPkDecryption().setPrivateKey(privateKey, error: nil) + + // Encrypt some data + let encryption = OLMPkEncryption() + encryption.setRecipientKey(publicKey) + let message = MXTools.serialiseJSONObject([ + "session_key": "SESSION_KEY_ABC" + ])! + let encrypted = encryption.encryptMessage(message, error: nil) + + // Prepare encrypted backup keys + let keys = MXKeysBackupData(fromJSON: [ + "rooms": [ + "A": [ + "sessions": [ + "1": [ + "session_data": [ + "ciphertext": encrypted.ciphertext, + "mac": encrypted.mac, + "ephemeral": encrypted.ephemeralKey + ] + ], + "2": [ + "session_data": [ + "ciphertext": encrypted.ciphertext, + "mac": encrypted.mac, + "ephemeral": encrypted.ephemeralKey + ] + ] + ] + ], + "B": [ + "sessions": [ + "3": [ + "session_data": [ + "ciphertext": encrypted.ciphertext, + "mac": encrypted.mac, + "ephemeral": encrypted.ephemeralKey + ] + ] + ] + ], + ] + ])! + + // Import keys + let exp = expectation(description: "exp") + engine.importKeys( + with: keys, + privateKey: privateKey, + keyBackupVersion: .stub(), + success: { total, imported in + + XCTAssertEqual(total, 3) + XCTAssertEqual(imported, 3) + + let decryptedKeys = self.backup.roomKeysSpy! + let sessionKeys = decryptedKeys.compactMap { $0.sessionKey } + let untrusted = decryptedKeys.compactMap { $0.isUntrusted } + let sessionIds = decryptedKeys.compactMap { $0.sessionId } + let roomIds = decryptedKeys.compactMap { $0.roomId } + + XCTAssertEqual(decryptedKeys.count, 3) + XCTAssertEqual(Set(sessionKeys), ["SESSION_KEY_ABC", "SESSION_KEY_ABC", "SESSION_KEY_ABC"]) + XCTAssertEqual(Set(untrusted), [true, true, true]) + XCTAssertEqual(Set(sessionIds), ["1", "2", "3"]) + XCTAssertEqual(Set(roomIds), ["A", "B"]) + + exp.fulfill() + }, + failure: { + XCTFail("Importing failed with error \($0)") + exp.fulfill() + } + ) + + waitForExpectations(timeout: 1) + } +} + +#endif diff --git a/MatrixSDKTests/TestPlans/UnitTests.xctestplan b/MatrixSDKTests/TestPlans/UnitTests.xctestplan index 95bffdaeed..0d18708cd2 100644 --- a/MatrixSDKTests/TestPlans/UnitTests.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTests.xctestplan @@ -41,6 +41,7 @@ "MXCrossSigningInfoSourceUnitTests", "MXCrossSigningInfoUnitTests", "MXCrossSigningV2UnitTests", + "MXCryptoKeyBackupEngineUnitTests", "MXCryptoRequestsUnitTests", "MXDeviceInfoSourceUnitTests", "MXDeviceInfoUnitTests", diff --git a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan index 21cfbceaec..489dc7ba49 100644 --- a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan @@ -51,6 +51,7 @@ "MXCrossSigningInfoSourceUnitTests", "MXCrossSigningInfoUnitTests", "MXCrossSigningV2UnitTests", + "MXCryptoKeyBackupEngineUnitTests", "MXCryptoRequestsUnitTests", "MXDeviceInfoSourceUnitTests", "MXDeviceInfoUnitTests", diff --git a/changelog.d/6769.change b/changelog.d/6769.change new file mode 100644 index 0000000000..9821feb280 --- /dev/null +++ b/changelog.d/6769.change @@ -0,0 +1 @@ +CryptoV2: Key backups From ea14cef2e29ecca9ebdc987e0883d3c9db8be8c1 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 29 Sep 2022 15:00:15 +0100 Subject: [PATCH 18/30] Rename method --- .../CryptoMachine/MXCryptoMachine.swift | 22 +++++++++---------- MatrixSDK/Crypto/MXCryptoV2.swift | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index 8789927d1c..21afd8f80f 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -60,8 +60,8 @@ class MXCryptoMachine { private let syncQueue = MXTaskQueue() private var roomQueues = RoomQueues() - private var hasUploadedKeys = false - private var keysUploadCallback: (() -> Void)? + private var hasUploadedInitialKeys = false + private var initialKeysUploadCallback: (() -> Void)? private let log = MXNamedLog(name: "MXCryptoMachine") @@ -79,13 +79,13 @@ class MXCryptoMachine { setLogger(logger: self) } - func onKeysUpload(callback: @escaping () -> Void) { + func onInitialKeysUpload(callback: @escaping () -> Void) { log.debug("->") - if hasUploadedKeys { + if hasUploadedInitialKeys { callback() } else { - keysUploadCallback = callback + initialKeysUploadCallback = callback } } @@ -189,7 +189,7 @@ extension MXCryptoMachine: MXCryptoSyncing { request: .init(body: body, deviceId: machine.deviceId()) ) try markRequestAsSent(requestId: requestId, requestType: .keysUpload, response: response.jsonString()) - broadcastKeysUploadIfNecessary() + broadcastInitialKeysUploadIfNecessary() case .keysQuery(let requestId, let users): let response = try await requests.queryKeys(users: users) @@ -259,13 +259,13 @@ extension MXCryptoMachine: MXCryptoSyncing { ) } - private func broadcastKeysUploadIfNecessary() { - guard !hasUploadedKeys else { + private func broadcastInitialKeysUploadIfNecessary() { + guard !hasUploadedInitialKeys else { return } - hasUploadedKeys = true - keysUploadCallback?() - keysUploadCallback = nil + hasUploadedInitialKeys = true + initialKeysUploadCallback?() + initialKeysUploadCallback = nil } } diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index e0f9083138..e80c206659 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -219,7 +219,7 @@ private class MXCryptoV2: MXCrypto { failure: ((Swift.Error?) -> Void)! ) { onComplete?() - machine.onKeysUpload { [weak self] in + machine.onInitialKeysUpload { [weak self] in guard let self = self else { return } self.crossSign.refreshState(success: nil) From f7615441b1c1035eb5877615a12b331f98456df0 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Wed, 28 Sep 2022 12:16:21 +0100 Subject: [PATCH 19/30] Key gossiping with Crypto V2 --- .../CryptoMachine/MXCryptoMachine.swift | 58 +++++++++++++++++-- .../CryptoMachine/MXCryptoProtocols.swift | 7 ++- MatrixSDK/Crypto/MXCryptoV2.swift | 41 ++++++++----- changelog.d/6773.change | 1 + 4 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 changelog.d/6773.change diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index 21afd8f80f..97c2f77726 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -315,7 +315,7 @@ extension MXCryptoMachine: MXCryptoUserIdentitySource { } } -extension MXCryptoMachine: MXCryptoEventEncrypting { +extension MXCryptoMachine: MXCryptoRoomEventEncrypting { func shareRoomKeysIfNecessary(roomId: String, users: [String]) async throws { try await sessionsQueue.sync { [weak self] in try await self?.updateTrackedUsers(users: users) @@ -328,7 +328,12 @@ extension MXCryptoMachine: MXCryptoEventEncrypting { } } - func encrypt(_ content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] { + func encryptRoomEvent( + content: [AnyHashable : Any], + roomId: String, + eventType: String, + users: [String] + ) async throws -> [String : Any] { guard let content = MXTools.serialiseJSONObject(content) else { throw Error.cannotSerialize } @@ -338,13 +343,54 @@ extension MXCryptoMachine: MXCryptoEventEncrypting { return MXTools.deserialiseJSONString(event) as? [String: Any] ?? [:] } - func decryptEvent(_ event: MXEvent) throws -> MXEventDecryptionResult { - guard let roomId = event.roomId, let event = event.jsonString() else { + func decryptRoomEvent(_ event: MXEvent) -> MXEventDecryptionResult { + guard let roomId = event.roomId, let eventString = event.jsonString() else { + log.failure("Invalid event") + + let result = MXEventDecryptionResult() + result.error = Error.invalidEvent + return result + } + + do { + let decryptedEvent = try machine.decryptRoomEvent(event: eventString, roomId: roomId) + let result = try MXEventDecryptionResult(event: decryptedEvent) + log.debug("Successfully decrypted event") + return result + + // `Megolm` error does not currently expose the type of "missing keys" error, so have to match against + // hardcoded non-localized error message. Will be changed in future PR + } catch DecryptionError.Megolm(message: "decryption failed because the room key is missing") { + let result = MXEventDecryptionResult() + result.error = NSError( + domain: MXDecryptingErrorDomain, + code: Int(MXDecryptingErrorUnknownInboundSessionIdCode.rawValue), + userInfo: [ + NSLocalizedDescriptionKey: MXDecryptingErrorUnknownInboundSessionIdReason + ] + ) + log.error("Failed decrypting due to missing key") + return result + } catch { + let result = MXEventDecryptionResult() + result.error = error + log.error("Failed decrypting", context: error) + return result + } + } + + func requestRoomKey(event: MXEvent) async throws { + guard let roomId = event.roomId, let eventString = event.jsonString() else { throw Error.invalidEvent } - let result = try machine.decryptRoomEvent(event: event, roomId: roomId) - return try MXEventDecryptionResult(event: result) + log.debug("->") + let result = try machine.requestRoomKey(event: eventString, roomId: roomId) + if let cancellation = result.cancellation { + try await handleRequest(cancellation) + } + try await handleRequest(result.keyRequest) + } func discardRoomKey(roomId: String) { diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift index df92892587..6437cea35d 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift @@ -56,10 +56,11 @@ protocol MXCryptoUserIdentitySource: MXCryptoIdentity { } /// Event encryption and decryption -protocol MXCryptoEventEncrypting: MXCryptoIdentity { +protocol MXCryptoRoomEventEncrypting: MXCryptoIdentity { func shareRoomKeysIfNecessary(roomId: String, users: [String]) async throws - func encrypt(_ content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] - func decryptEvent(_ event: MXEvent) throws -> MXEventDecryptionResult + func encryptRoomEvent(content: [AnyHashable: Any], roomId: String, eventType: String, users: [String]) async throws -> [String: Any] + func decryptRoomEvent(_ event: MXEvent) -> MXEventDecryptionResult + func requestRoomKey(event: MXEvent) async throws func discardRoomKey(roomId: String) } diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index e80c206659..0529d14d40 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -257,8 +257,8 @@ private class MXCryptoV2: MXCrypto { Task { do { let users = try await getRoomUserIds(for: room) - let result = try await machine.encrypt( - content, + let result = try await machine.encryptRoomEvent( + content: content, roomId: roomId, eventType: eventType, users: users @@ -283,20 +283,14 @@ private class MXCryptoV2: MXCrypto { ) -> MXEventDecryptionResult! { guard let event = event else { log.failure("Missing event") - return nil + return MXEventDecryptionResult() } - do { - let result = try machine.decryptEvent(event) - let type = result.clearEvent["type"] ?? "" - log.debug("Decrypted event of type `\(type)`") - - return result - } catch { - log.error("Error decrypting event", context: error) - let result = MXEventDecryptionResult() - result.error = error - return result + guard event.isEncrypted && event.content?["algorithm"] as? String == kMXCryptoMegolmAlgorithm else { + log.debug("Ignoring non-room event") + return MXEventDecryptionResult() } + + return machine.decryptRoomEvent(event) } public override func decryptEvents( @@ -611,7 +605,24 @@ private class MXCryptoV2: MXCrypto { } public override func reRequestRoomKey(for event: MXEvent!) { - log.debug("Not implemented") + guard let event = event else { + log.failure("Missing event") + return + } + + Task { + log.debug("->") + do { + try await machine.requestRoomKey(event: event) + await MainActor.run { + let result = decryptEvent(event, inTimeline: nil) + event.setClearData(result) + log.debug("Recieved room keys and re-decrypted event") + } + } catch { + log.error("Failed requesting room key", context: error) + } + } } public override var warnOnUnknowDevices: Bool { diff --git a/changelog.d/6773.change b/changelog.d/6773.change new file mode 100644 index 0000000000..670fcfdd03 --- /dev/null +++ b/changelog.d/6773.change @@ -0,0 +1 @@ +CryptoV2: Key gossiping From d0a5f994333e294c2eb71703e3e7233b34837bfb Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Thu, 29 Sep 2022 18:05:32 +0300 Subject: [PATCH 20/30] Move client information logic into a dedicated service, add tests --- MatrixSDK.xcodeproj/project.pbxproj | 20 ++++ .../MXClientInformationService.swift | 90 ++++++++++++++++++ MatrixSDK/MXSession.m | 57 +----------- .../MXClientInformationServiceUnitTests.swift | 91 +++++++++++++++++++ MatrixSDKTests/Mocks/MXRestClientStub.m | 18 ++++ MatrixSDKTests/TestPlans/UnitTests.xctestplan | 1 + .../UnitTestsWithSanitizers.xctestplan | 1 + 7 files changed, 225 insertions(+), 53 deletions(-) create mode 100644 MatrixSDK/ClientInformation/MXClientInformationService.swift create mode 100644 MatrixSDKTests/MXClientInformationServiceUnitTests.swift diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 4c0e57a917..05f95de792 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -1791,6 +1791,10 @@ ECDA764D27BA963D000C48CF /* MXBooleanCapability.h in Headers */ = {isa = PBXBuildFile; fileRef = ECDA764A27BA963D000C48CF /* MXBooleanCapability.h */; settings = {ATTRIBUTES = (Public, ); }; }; ECDA764E27BA963D000C48CF /* MXBooleanCapability.m in Sources */ = {isa = PBXBuildFile; fileRef = ECDA764B27BA963D000C48CF /* MXBooleanCapability.m */; }; ECDA764F27BA963D000C48CF /* MXBooleanCapability.m in Sources */ = {isa = PBXBuildFile; fileRef = ECDA764B27BA963D000C48CF /* MXBooleanCapability.m */; }; + ECDBE69028E5D961000C83AF /* MXClientInformationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDBE68F28E5D961000C83AF /* MXClientInformationService.swift */; }; + ECDBE69128E5DC9A000C83AF /* MXClientInformationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDBE68F28E5D961000C83AF /* MXClientInformationService.swift */; }; + ECDBE69328E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDBE69228E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift */; }; + ECDBE69428E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDBE69228E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift */; }; ECE3DF842707370000FB4C96 /* MXRoomListDataPaginationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE3DF832707370000FB4C96 /* MXRoomListDataPaginationOptions.swift */; }; ECE3DF852707370000FB4C96 /* MXRoomListDataPaginationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE3DF832707370000FB4C96 /* MXRoomListDataPaginationOptions.swift */; }; ECE3DF9D270C660900FB4C96 /* MXMulticastDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE3DF9C270C660900FB4C96 /* MXMulticastDelegate.swift */; }; @@ -2948,6 +2952,8 @@ ECDA764527BA939E000C48CF /* MXRoomVersionsCapability.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRoomVersionsCapability.m; sourceTree = ""; }; ECDA764A27BA963D000C48CF /* MXBooleanCapability.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXBooleanCapability.h; sourceTree = ""; }; ECDA764B27BA963D000C48CF /* MXBooleanCapability.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXBooleanCapability.m; sourceTree = ""; }; + ECDBE68F28E5D961000C83AF /* MXClientInformationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXClientInformationService.swift; sourceTree = ""; }; + ECDBE69228E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXClientInformationServiceUnitTests.swift; sourceTree = ""; }; ECE3DF7E2707299C00FB4C96 /* MXSuggestedRoomListDataFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSuggestedRoomListDataFetcher.swift; sourceTree = ""; }; ECE3DF8027072BE500FB4C96 /* MXSuggestedRoomListDataCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSuggestedRoomListDataCache.swift; sourceTree = ""; }; ECE3DF832707370000FB4C96 /* MXRoomListDataPaginationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomListDataPaginationOptions.swift; sourceTree = ""; }; @@ -4110,6 +4116,7 @@ 32C6F92F19DD814400EA4E9C /* MatrixSDK */ = { isa = PBXGroup; children = ( + ECDBE68E28E5D94C000C83AF /* ClientInformation */, 66836AB427CFA17200515780 /* EventStream */, 3A858DD82750EE0A006322C1 /* HomeServer */, ECCA02B9273485A100B6F34F /* Threads */, @@ -4172,6 +4179,7 @@ 32B477452638128700EA5800 /* MXJSONModelUnitTests.m */, 32B090FC26201C8D002924AA /* MXAsyncTaskQueueUnitTests.swift */, 32EEA8492603FDD60041425B /* MXResponseUnitTests.swift */, + ECDBE69228E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift */, 32EEA83E2603CA140041425B /* MXRestClientExtensionsTests.m */, 32C78BA6256D227D008130B1 /* MXCryptoMigrationTests.m */, 321EA11C24893A0400E35B02 /* MXCryptoRecoveryServiceTests.m */, @@ -5191,6 +5199,14 @@ path = Capabilities; sourceTree = ""; }; + ECDBE68E28E5D94C000C83AF /* ClientInformation */ = { + isa = PBXGroup; + children = ( + ECDBE68F28E5D961000C83AF /* MXClientInformationService.swift */, + ); + path = ClientInformation; + sourceTree = ""; + }; ECE3DF7D2707292500FB4C96 /* Common */ = { isa = PBXGroup; children = ( @@ -6595,6 +6611,7 @@ 8EC511062568216B00EC4E5B /* MXTaggedEventInfo.m in Sources */, 3AC135DB2640335100EE1E74 /* MXDehydrationService.m in Sources */, 32792BDD2296B90A00F4FC9D /* MXAggregatedEditsUpdater.m in Sources */, + ECDBE69028E5D961000C83AF /* MXClientInformationService.swift in Sources */, ED44F01428180EAB00452A5D /* MXSharedHistoryKeyManager.swift in Sources */, 3259CD541DF860C300186944 /* MXRealmCryptoStore.m in Sources */, ED44F01128180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */, @@ -7033,6 +7050,7 @@ ED2DD11D286C4F4400F06731 /* MXCryptoRequestsUnitTests.swift in Sources */, 32832B5D1BCC048300241108 /* MXStoreMemoryStoreTests.m in Sources */, EDB4209927DF842F0036AF39 /* MXEventFixtures.swift in Sources */, + ECDBE69328E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift in Sources */, 32114A7F1A24E15500FF2EC4 /* MXMyUserTests.m in Sources */, 32832B5E1BCC048300241108 /* MXStoreNoStoreTests.m in Sources */, A816247C25F60C7700A46F05 /* MXDeviceListOperationsPoolUnitTests.swift in Sources */, @@ -7210,6 +7228,7 @@ B14EF1E82397E90400758AF0 /* MXRoomPowerLevels.m in Sources */, 32EEA85E260401490041425B /* MXSummable.swift in Sources */, B14EF1E92397E90400758AF0 /* MXRealmMediaScanMapper.m in Sources */, + ECDBE69128E5DC9A000C83AF /* MXClientInformationService.swift in Sources */, ED44F01528180EAB00452A5D /* MXSharedHistoryKeyManager.swift in Sources */, EC8A53E725B1BCC6004E0802 /* MXThirdPartyProtocol.m in Sources */, ED44F01228180BCC00452A5D /* MXSharedHistoryKeyRequest.swift in Sources */, @@ -7648,6 +7667,7 @@ B1E09A1A2397FCE90057C069 /* MXAggregatedEditsTests.m in Sources */, B1E09A1F2397FCE90057C069 /* MXAutoDiscoveryTests.m in Sources */, EDB4209A27DF842F0036AF39 /* MXEventFixtures.swift in Sources */, + ECDBE69428E5E16F000C83AF /* MXClientInformationServiceUnitTests.swift in Sources */, B1E09A2E2397FD750057C069 /* MXRestClientTests.m in Sources */, 32C9B71923E81A1C00C6F30A /* MXCrossSigningVerificationTests.m in Sources */, B1E09A1D2397FCE90057C069 /* MXCryptoKeyVerificationTests.m in Sources */, diff --git a/MatrixSDK/ClientInformation/MXClientInformationService.swift b/MatrixSDK/ClientInformation/MXClientInformationService.swift new file mode 100644 index 0000000000..96527fb982 --- /dev/null +++ b/MatrixSDK/ClientInformation/MXClientInformationService.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 + +@objcMembers +public class MXClientInformationService: NSObject { + + private weak var session: MXSession? + + public init(withSession session: MXSession) { + self.session = session + } + + public func refresh() { + guard MXSDKOptions.sharedInstance().enableNewClientInformationFeature else { + return removeDataIfNeeded() + } + + guard let session = session else { + return + } + + guard let bundleDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String, + let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { + return + } + + let updatedInfo: [AnyHashable: String] = [ + "name": "\(bundleDisplayName) iOS", + "version": appVersion + ] + + let type = accountDataType(for: session) + let currentInfo = session.accountData.accountData(forEventType: type) + + guard !NSDictionary(dictionary: updatedInfo).isEqual(to: currentInfo) else { + MXLog.debug("[MXClientInformationService] refresh: no need to update") + return + } + + session.setAccountData(updatedInfo, forType: type) { + MXLog.debug("[MXClientInformationService] refresh: updated successfully") + } failure: { error in + MXLog.debug("[MXClientInformationService] refresh: update failed: \(String(describing: error))") + } + } + + private func removeDataIfNeeded() { + guard let session = session else { + return + } + + let type = accountDataType(for: session) + + guard let currentInfo = session.accountData.accountData(forEventType: type), + !currentInfo.isEmpty else { + // not exists, no need to do anything + MXLog.debug("[MXClientInformationService] removeDataIfNeeded: no need to remove") + return + } + + session.setAccountData([:], forType: type) { + MXLog.debug("[MXClientInformationService] removeDataIfNeeded: removed successfully") + } failure: { error in + MXLog.debug("[MXClientInformationService] removeDataIfNeeded: remove failed: \(String(describing: error))") + } + + } + + private func accountDataType(for session: MXSession) -> String { + guard let deviceId = session.myDeviceId else { + fatalError("[MXClientInformationService] No device id") + } + return "\(kMXAccountDataTypeClientInformation).\(deviceId)" + } +} diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index efadbd7ae0..6e1eac4314 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -215,6 +215,8 @@ Queue of requested direct room change operations ([MXSession setRoom:directWithU @property (nonatomic, readonly) MXStoreService *storeService; +@property (nonatomic, readwrite) MXClientInformationService *clientInformationService; + @end @implementation MXSession @@ -254,6 +256,7 @@ - (id)initWithMatrixRestClient:(MXRestClient*)mxRestClient _eventStreamService = [[MXEventStreamService alloc] init]; _preferredSyncPresence = MXPresenceOnline; _locationService = [[MXLocationService alloc] initWithSession:self]; + _clientInformationService = [[MXClientInformationService alloc] initWithSession:self]; [self setIdentityServer:mxRestClient.identityServer andAccessToken:mxRestClient.credentials.identityServerAccessToken]; @@ -1138,7 +1141,7 @@ - (void)_resume:(void (^)(void))resumeDone [self serverSyncWithServerTimeout:0 success:nil failure:nil clientTimeout:CLIENT_TIMEOUT_MS setPresence:self.preferredSyncPresenceString]; } - [self refreshClientInformationIfNeeded]; + [self.clientInformationService refresh]; } for (MXPeekingRoom *peekingRoom in peekingRooms) @@ -4561,58 +4564,6 @@ - (void)updateBreadcrumbsWithRoomWithId:(NSString *)roomId }]; } -- (void)refreshClientInformationIfNeeded -{ - if (!MXSDKOptions.sharedInstance.enableNewClientInformationFeature) - { - [self removeClientInformationIfNeeded]; - return; - } - NSString *bundleDisplayName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; - NSString *name = [NSString stringWithFormat:@"%@ iOS", bundleDisplayName]; - NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; - NSDictionary *updatedInfo = @{ - @"name": name, - @"version": version - }; - NSString *type = [NSString stringWithFormat:@"%@.%@", kMXAccountDataTypeClientInformation, self.myDeviceId]; - - NSDictionary *currentInfo = [self.accountData accountDataForEventType:type]; - - if ([updatedInfo isEqualToDictionary:currentInfo]) - { - MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: no need to update"); - } - else - { - // there is change, update the event - [self setAccountData:updatedInfo forType:type success:^{ - MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: updated successfully"); - } failure:^(NSError *error) { - MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: update failed: %@", error); - }]; - } -} - -- (void)removeClientInformationIfNeeded -{ - NSString *type = [NSString stringWithFormat:@"%@.%@", kMXAccountDataTypeClientInformation, self.myDeviceId]; - NSDictionary *currentInfo = [self.accountData accountDataForEventType:type]; - if (currentInfo.count) - { - // remove current account data regarding client information - [self setAccountData:@{} forType:type success:^{ - MXLogDebug(@"[MXSession] removeClientInformationIfNeeded: removed successfully"); - } failure:^(NSError *error) { - MXLogDebug(@"[MXSession] removeClientInformationIfNeeded: remove failed: %@", error); - }]; - } - else - { - MXLogDebug(@"[MXSession] refreshClientInformationIfNeeded: no need to remove"); - } -} - #pragma mark - Homeserver information - (MXWellKnown *)homeserverWellknown { diff --git a/MatrixSDKTests/MXClientInformationServiceUnitTests.swift b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift new file mode 100644 index 0000000000..f4c04a1848 --- /dev/null +++ b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift @@ -0,0 +1,91 @@ +// +// 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 XCTest +import MatrixSDK + +class MXClientInformationServiceUnitTests: XCTestCase { + + func testRefresh() { + MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true + + let mockDeviceId = "some_device_id" + let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") + credentials.deviceId = mockDeviceId + guard let session = MockSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + XCTFail("Failed to setup test conditions") + return + } + let type = "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)" + + // no client info before + let clientInfo = session.accountData.accountData(forEventType: type) + XCTAssertNil(clientInfo) + + session.resume { + + } + + // now must be set + let updatedInfo = session.accountData.accountData(forEventType: type) + XCTAssertNotNil(updatedInfo) + + session.close() + } + + func testRemove() { + // enable the feature + MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true + + let mockDeviceId = "some_device_id" + let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") + credentials.deviceId = mockDeviceId + guard let session = MockSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + XCTFail("Failed to setup test conditions") + return + } + let type = "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)" + + session.resume { + + } + + let clientInfo = session.accountData.accountData(forEventType: type) + XCTAssertNotNil(clientInfo) + + // disable the feature + MXSDKOptions.sharedInstance().enableNewClientInformationFeature = false + + session.resume { + + } + + let updatedInfo = session.accountData.accountData(forEventType: type) + XCTAssert(updatedInfo?.isEmpty ?? true) + + session.close() + } +} + +fileprivate class MockSession: MXSession { + override var isResumable: Bool { + true + } + + override func handleBackgroundSyncCacheIfRequired(completion: (() -> Void)!) { + completion?() + } +} diff --git a/MatrixSDKTests/Mocks/MXRestClientStub.m b/MatrixSDKTests/Mocks/MXRestClientStub.m index a9a2a39f65..963eebb66f 100644 --- a/MatrixSDKTests/Mocks/MXRestClientStub.m +++ b/MatrixSDKTests/Mocks/MXRestClientStub.m @@ -25,4 +25,22 @@ - (MXHTTPOperation *)stateOfRoom:(NSString *)roomId success:(void (^)(NSArray *) return [[MXHTTPOperation alloc] init]; } +- (MXHTTPOperation *)setAccountData:(NSDictionary *)data forType:(NSString *)type success:(void (^)(void))success failure:(void (^)(NSError *))failure +{ + if (success) + { + success(); + } + return [[MXHTTPOperation alloc] init]; +} + +- (MXHTTPOperation *)syncFromToken:(NSString *)token serverTimeout:(NSUInteger)serverTimeout clientTimeout:(NSUInteger)clientTimeout setPresence:(NSString *)setPresence filter:(NSString *)filterId success:(void (^)(MXSyncResponse *))success failure:(void (^)(NSError *))failure +{ + if (success) + { + success(nil); + } + return [[MXHTTPOperation alloc] init]; +} + @end diff --git a/MatrixSDKTests/TestPlans/UnitTests.xctestplan b/MatrixSDKTests/TestPlans/UnitTests.xctestplan index 95bffdaeed..b3c8bcb84c 100644 --- a/MatrixSDKTests/TestPlans/UnitTests.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTests.xctestplan @@ -36,6 +36,7 @@ "MXAuthenticationSessionUnitTests", "MXBackgroundTaskUnitTests", "MXBeaconInfoUnitTests", + "MXClientInformationServiceUnitTests", "MXCoreDataRoomListDataManagerUnitTests", "MXCredentialsUnitTests", "MXCrossSigningInfoSourceUnitTests", diff --git a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan index 21cfbceaec..58c947aa4b 100644 --- a/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan +++ b/MatrixSDKTests/TestPlans/UnitTestsWithSanitizers.xctestplan @@ -46,6 +46,7 @@ "MXAuthenticationSessionUnitTests", "MXBackgroundTaskUnitTests", "MXBeaconInfoUnitTests", + "MXClientInformationServiceUnitTests", "MXCoreDataRoomListDataManagerUnitTests", "MXCredentialsUnitTests", "MXCrossSigningInfoSourceUnitTests", From 5dfecac6f44df96b7cb65f89e82dd00f6bdaa759 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 30 Sep 2022 11:57:40 +0300 Subject: [PATCH 21/30] Fix PR comments --- .../MXClientInformationService.swift | 38 +++--- MatrixSDK/MXSession.m | 2 +- .../MXClientInformationServiceUnitTests.swift | 116 ++++++++++++++---- 3 files changed, 116 insertions(+), 40 deletions(-) diff --git a/MatrixSDK/ClientInformation/MXClientInformationService.swift b/MatrixSDK/ClientInformation/MXClientInformationService.swift index 96527fb982..e3629c113b 100644 --- a/MatrixSDK/ClientInformation/MXClientInformationService.swift +++ b/MatrixSDK/ClientInformation/MXClientInformationService.swift @@ -25,24 +25,18 @@ public class MXClientInformationService: NSObject { self.session = session } - public func refresh() { - guard MXSDKOptions.sharedInstance().enableNewClientInformationFeature else { - return removeDataIfNeeded() - } - + public func updateData() { guard let session = session else { return } - guard let bundleDisplayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String, - let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { - return + guard MXSDKOptions.sharedInstance().enableNewClientInformationFeature else { + return removeDataIfNeeded(on: session) } - let updatedInfo: [AnyHashable: String] = [ - "name": "\(bundleDisplayName) iOS", - "version": appVersion - ] + guard let updatedInfo = createClientInformation() else { + return + } let type = accountDataType(for: session) let currentInfo = session.accountData.accountData(forEventType: type) @@ -59,11 +53,7 @@ public class MXClientInformationService: NSObject { } } - private func removeDataIfNeeded() { - guard let session = session else { - return - } - + internal func removeDataIfNeeded(on session: MXSession) { let type = accountDataType(for: session) guard let currentInfo = session.accountData.accountData(forEventType: type), @@ -78,10 +68,22 @@ public class MXClientInformationService: NSObject { } failure: { error in MXLog.debug("[MXClientInformationService] removeDataIfNeeded: remove failed: \(String(describing: error))") } + } + + internal func createClientInformation() -> [AnyHashable: String]? { + guard let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") + ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String, + let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { + return nil + } + return [ + "name": "\(name) iOS", + "version": version + ] } - private func accountDataType(for session: MXSession) -> String { + internal func accountDataType(for session: MXSession) -> String { guard let deviceId = session.myDeviceId else { fatalError("[MXClientInformationService] No device id") } diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 6e1eac4314..248fb2a5e9 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -1141,7 +1141,7 @@ - (void)_resume:(void (^)(void))resumeDone [self serverSyncWithServerTimeout:0 success:nil failure:nil clientTimeout:CLIENT_TIMEOUT_MS setPresence:self.preferredSyncPresenceString]; } - [self.clientInformationService refresh]; + [self.clientInformationService updateData]; } for (MXPeekingRoom *peekingRoom in peekingRooms) diff --git a/MatrixSDKTests/MXClientInformationServiceUnitTests.swift b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift index f4c04a1848..d6ab2207a1 100644 --- a/MatrixSDKTests/MXClientInformationServiceUnitTests.swift +++ b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift @@ -15,53 +15,97 @@ // import XCTest -import MatrixSDK +@testable import MatrixSDK class MXClientInformationServiceUnitTests: XCTestCase { - func testRefresh() { + func testUpdateData() { MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true let mockDeviceId = "some_device_id" let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") credentials.deviceId = mockDeviceId - guard let session = MockSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { XCTFail("Failed to setup test conditions") return } - let type = "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)" + + let service = MXClientInformationService(withSession: session) + + let type = service.accountDataType(for: session) // no client info before let clientInfo = session.accountData.accountData(forEventType: type) XCTAssertNil(clientInfo) - session.resume { + service.updateData() + + // must be set after updateData + let updatedInfo1 = session.accountData.accountData(forEventType: type) + XCTAssertNotNil(updatedInfo1) + + // try updating again + service.updateData() + + // must not be changed + let updatedInfo2 = session.accountData.accountData(forEventType: type) + XCTAssertTrue(NSDictionary(dictionary: updatedInfo1!).isEqual(to: updatedInfo2)) + + session.close() + } + func testRemoveData() { + let mockDeviceId = "some_device_id" + let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") + credentials.deviceId = mockDeviceId + guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + XCTFail("Failed to setup test conditions") + return } - // now must be set + let service = MXClientInformationService(withSession: session) + + let type = service.accountDataType(for: session) + + session.setAccountData(["some_key": "some_value"], forType: type) { + + } failure: { _ in + XCTFail("Failed to setup test conditions") + } + + service.removeDataIfNeeded(on: session) + + // must be empty after removeDataIfNeeded let updatedInfo = session.accountData.accountData(forEventType: type) - XCTAssertNotNil(updatedInfo) + XCTAssert(updatedInfo?.isEmpty ?? true) + + // remove data again when empty + service.removeDataIfNeeded(on: session) + + // must be still empty + let updatedInfo2 = session.accountData.accountData(forEventType: type) + XCTAssert(updatedInfo2?.isEmpty ?? true) session.close() } - func testRemove() { + func testRemoveDataByDisablingFeature() { // enable the feature MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true let mockDeviceId = "some_device_id" let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") credentials.deviceId = mockDeviceId - guard let session = MockSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { XCTFail("Failed to setup test conditions") return } - let type = "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)" - session.resume { + let service = MXClientInformationService(withSession: session) - } + let type = service.accountDataType(for: session) + + service.updateData() let clientInfo = session.accountData.accountData(forEventType: type) XCTAssertNotNil(clientInfo) @@ -69,23 +113,53 @@ class MXClientInformationServiceUnitTests: XCTestCase { // disable the feature MXSDKOptions.sharedInstance().enableNewClientInformationFeature = false - session.resume { - - } + service.updateData() + // must be empty after updateData let updatedInfo = session.accountData.accountData(forEventType: type) XCTAssert(updatedInfo?.isEmpty ?? true) session.close() } -} -fileprivate class MockSession: MXSession { - override var isResumable: Bool { - true + func testClientInformation() { + // enable the feature + MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true + + let mockDeviceId = "some_device_id" + let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") + credentials.deviceId = mockDeviceId + guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + XCTFail("Failed to setup test conditions") + return + } + + let service = MXClientInformationService(withSession: session) + let clientInfo = service.createClientInformation() + + XCTAssertNotNil(clientInfo?["name"]) + XCTAssertNotNil(clientInfo?["version"]) + XCTAssertNil(clientInfo?["url"]) + + session.close() } - override func handleBackgroundSyncCacheIfRequired(completion: (() -> Void)!) { - completion?() + func testAccountDataType() { + // enable the feature + MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true + + let mockDeviceId = "some_device_id" + let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") + credentials.deviceId = mockDeviceId + guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + XCTFail("Failed to setup test conditions") + return + } + + let service = MXClientInformationService(withSession: session) + + XCTAssertEqual(service.accountDataType(for:session), "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)") + + session.close() } } From 03ba98deeba4b9ed28d00d83059f9c60ea71213c Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 30 Sep 2022 13:13:03 +0300 Subject: [PATCH 22/30] Further PR comments --- .../MXClientInformationServiceUnitTests.swift | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/MatrixSDKTests/MXClientInformationServiceUnitTests.swift b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift index d6ab2207a1..bf89f30452 100644 --- a/MatrixSDKTests/MXClientInformationServiceUnitTests.swift +++ b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift @@ -43,13 +43,34 @@ class MXClientInformationServiceUnitTests: XCTestCase { // must be set after updateData let updatedInfo1 = session.accountData.accountData(forEventType: type) XCTAssertNotNil(updatedInfo1) + XCTAssertFalse(updatedInfo1!.isEmpty) - // try updating again + session.close() + } + + func testRedundantUpdateData() { + MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true + + let mockDeviceId = "some_device_id" + let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") + credentials.deviceId = mockDeviceId + guard let session = MockSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + XCTFail("Failed to setup test conditions") + return + } + + let service = MXClientInformationService(withSession: session) + + let type = service.accountDataType(for: session) + let newClientInfo = service.createClientInformation() + + // set account data internally + session.accountData.update(withType: type, data: newClientInfo) + + // make a redundant update service.updateData() - // must not be changed - let updatedInfo2 = session.accountData.accountData(forEventType: type) - XCTAssertTrue(NSDictionary(dictionary: updatedInfo1!).isEqual(to: updatedInfo2)) + XCTAssertFalse(session.isSetAccountDataCalled) session.close() } @@ -163,3 +184,19 @@ class MXClientInformationServiceUnitTests: XCTestCase { session.close() } } + +private class MockSession: MXSession { + + var isSetAccountDataCalled = false + + override func setAccountData(_ data: [AnyHashable : Any]!, + forType type: String!, + success: (() -> Void)!, + failure: ((Error?) -> Void)!) -> MXHTTPOperation! { + isSetAccountDataCalled = true + return super.setAccountData(data, + forType: type, + success: success, + failure: failure) + } +} From 3bccb26c2b34625570e79ac83c1bec88f2be563c Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 30 Sep 2022 13:28:05 +0300 Subject: [PATCH 23/30] Fix pod linter issue --- MatrixSDK/ClientInformation/MXClientInformationService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MatrixSDK/ClientInformation/MXClientInformationService.swift b/MatrixSDK/ClientInformation/MXClientInformationService.swift index e3629c113b..3ee7624ec6 100644 --- a/MatrixSDK/ClientInformation/MXClientInformationService.swift +++ b/MatrixSDK/ClientInformation/MXClientInformationService.swift @@ -41,7 +41,7 @@ public class MXClientInformationService: NSObject { let type = accountDataType(for: session) let currentInfo = session.accountData.accountData(forEventType: type) - guard !NSDictionary(dictionary: updatedInfo).isEqual(to: currentInfo) else { + guard !NSDictionary(dictionary: updatedInfo).isEqual(to: currentInfo ?? [:]) else { MXLog.debug("[MXClientInformationService] refresh: no need to update") return } From b2f52a38a32248a6906aa14dbfca55330d664335 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Fri, 30 Sep 2022 15:01:31 +0300 Subject: [PATCH 24/30] Fix PR comments --- .../MXClientInformationService.swift | 17 ++- MatrixSDK/MXSession.m | 3 +- .../MXClientInformationServiceUnitTests.swift | 144 +++++------------- 3 files changed, 54 insertions(+), 110 deletions(-) diff --git a/MatrixSDK/ClientInformation/MXClientInformationService.swift b/MatrixSDK/ClientInformation/MXClientInformationService.swift index 3ee7624ec6..88edc6e7ee 100644 --- a/MatrixSDK/ClientInformation/MXClientInformationService.swift +++ b/MatrixSDK/ClientInformation/MXClientInformationService.swift @@ -20,9 +20,12 @@ import Foundation public class MXClientInformationService: NSObject { private weak var session: MXSession? + private let bundle: Bundle - public init(withSession session: MXSession) { + public init(withSession session: MXSession, + bundle: Bundle) { self.session = session + self.bundle = bundle } public func updateData() { @@ -53,7 +56,7 @@ public class MXClientInformationService: NSObject { } } - internal func removeDataIfNeeded(on session: MXSession) { + private func removeDataIfNeeded(on session: MXSession) { let type = accountDataType(for: session) guard let currentInfo = session.accountData.accountData(forEventType: type), @@ -70,10 +73,10 @@ public class MXClientInformationService: NSObject { } } - internal func createClientInformation() -> [AnyHashable: String]? { - guard let name = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") - ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String, - let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { + private func createClientInformation() -> [AnyHashable: String]? { + guard let name = bundle.object(forInfoDictionaryKey: "CFBundleDisplayName") + ?? bundle.object(forInfoDictionaryKey: "CFBundleName") as? String, + let version = bundle.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String else { return nil } @@ -83,7 +86,7 @@ public class MXClientInformationService: NSObject { ] } - internal func accountDataType(for session: MXSession) -> String { + private func accountDataType(for session: MXSession) -> String { guard let deviceId = session.myDeviceId else { fatalError("[MXClientInformationService] No device id") } diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 248fb2a5e9..43352ee5db 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -256,7 +256,8 @@ - (id)initWithMatrixRestClient:(MXRestClient*)mxRestClient _eventStreamService = [[MXEventStreamService alloc] init]; _preferredSyncPresence = MXPresenceOnline; _locationService = [[MXLocationService alloc] initWithSession:self]; - _clientInformationService = [[MXClientInformationService alloc] initWithSession:self]; + _clientInformationService = [[MXClientInformationService alloc] initWithSession:self + bundle:NSBundle.mainBundle]; [self setIdentityServer:mxRestClient.identityServer andAccessToken:mxRestClient.credentials.identityServerAccessToken]; diff --git a/MatrixSDKTests/MXClientInformationServiceUnitTests.swift b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift index bf89f30452..040b912007 100644 --- a/MatrixSDKTests/MXClientInformationServiceUnitTests.swift +++ b/MatrixSDKTests/MXClientInformationServiceUnitTests.swift @@ -19,20 +19,18 @@ import XCTest class MXClientInformationServiceUnitTests: XCTestCase { + let mockDeviceId = "some_device_id" + let mockAppName = "Element" + let mockAppVersion = "1.9.7" + func testUpdateData() { MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true - let mockDeviceId = "some_device_id" - let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") - credentials.deviceId = mockDeviceId - guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { - XCTFail("Failed to setup test conditions") - return - } + let (session, bundle) = createSessionAndBundle() - let service = MXClientInformationService(withSession: session) + let service = MXClientInformationService(withSession: session, bundle: bundle) - let type = service.accountDataType(for: session) + let type = "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)" // no client info before let clientInfo = session.accountData.accountData(forEventType: type) @@ -41,9 +39,9 @@ class MXClientInformationServiceUnitTests: XCTestCase { service.updateData() // must be set after updateData - let updatedInfo1 = session.accountData.accountData(forEventType: type) - XCTAssertNotNil(updatedInfo1) - XCTAssertFalse(updatedInfo1!.isEmpty) + let updatedInfo = session.accountData.accountData(forEventType: type) + XCTAssertEqual(updatedInfo?["name"] as? String, "\(mockAppName) iOS") + XCTAssertEqual(updatedInfo?["version"] as? String, mockAppVersion) session.close() } @@ -51,18 +49,15 @@ class MXClientInformationServiceUnitTests: XCTestCase { func testRedundantUpdateData() { MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true - let mockDeviceId = "some_device_id" - let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") - credentials.deviceId = mockDeviceId - guard let session = MockSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { - XCTFail("Failed to setup test conditions") - return - } + let (session, bundle) = createSessionAndBundle() - let service = MXClientInformationService(withSession: session) + let service = MXClientInformationService(withSession: session, bundle: bundle) - let type = service.accountDataType(for: session) - let newClientInfo = service.createClientInformation() + let type = "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)" + let newClientInfo = [ + "name": "\(mockAppName) iOS", + "version": mockAppVersion + ] // set account data internally session.accountData.update(withType: type, data: newClientInfo) @@ -75,56 +70,15 @@ class MXClientInformationServiceUnitTests: XCTestCase { session.close() } - func testRemoveData() { - let mockDeviceId = "some_device_id" - let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") - credentials.deviceId = mockDeviceId - guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { - XCTFail("Failed to setup test conditions") - return - } - - let service = MXClientInformationService(withSession: session) - - let type = service.accountDataType(for: session) - - session.setAccountData(["some_key": "some_value"], forType: type) { - - } failure: { _ in - XCTFail("Failed to setup test conditions") - } - - service.removeDataIfNeeded(on: session) - - // must be empty after removeDataIfNeeded - let updatedInfo = session.accountData.accountData(forEventType: type) - XCTAssert(updatedInfo?.isEmpty ?? true) - - // remove data again when empty - service.removeDataIfNeeded(on: session) - - // must be still empty - let updatedInfo2 = session.accountData.accountData(forEventType: type) - XCTAssert(updatedInfo2?.isEmpty ?? true) - - session.close() - } - func testRemoveDataByDisablingFeature() { // enable the feature MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true - let mockDeviceId = "some_device_id" - let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") - credentials.deviceId = mockDeviceId - guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { - XCTFail("Failed to setup test conditions") - return - } + let (session, bundle) = createSessionAndBundle() - let service = MXClientInformationService(withSession: session) + let service = MXClientInformationService(withSession: session, bundle: bundle) - let type = service.accountDataType(for: session) + let type = "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)" service.updateData() @@ -143,45 +97,18 @@ class MXClientInformationServiceUnitTests: XCTestCase { session.close() } - func testClientInformation() { - // enable the feature - MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true - - let mockDeviceId = "some_device_id" - let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") - credentials.deviceId = mockDeviceId - guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { - XCTFail("Failed to setup test conditions") - return - } - - let service = MXClientInformationService(withSession: session) - let clientInfo = service.createClientInformation() - - XCTAssertNotNil(clientInfo?["name"]) - XCTAssertNotNil(clientInfo?["version"]) - XCTAssertNil(clientInfo?["url"]) - - session.close() - } - - func testAccountDataType() { - // enable the feature - MXSDKOptions.sharedInstance().enableNewClientInformationFeature = true - - let mockDeviceId = "some_device_id" + // Returns (session, bundle) tuple + private func createSessionAndBundle() -> (MockSession, Bundle) { let credentials = MXCredentials(homeServer: "", userId: "@userid:example.com", accessToken: "") credentials.deviceId = mockDeviceId - guard let session = MXSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { - XCTFail("Failed to setup test conditions") - return + guard let session = MockSession(matrixRestClient: MXRestClientStub(credentials: credentials)) else { + fatalError("Cannot create session") } - - let service = MXClientInformationService(withSession: session) - - XCTAssertEqual(service.accountDataType(for:session), "\(kMXAccountDataTypeClientInformation).\(mockDeviceId)") - - session.close() + let bundle = MockBundle(with: [ + "CFBundleDisplayName": mockAppName, + "CFBundleShortVersionString": mockAppVersion + ]) + return (session, bundle) } } @@ -200,3 +127,16 @@ private class MockSession: MXSession { failure: failure) } } + +private class MockBundle: Bundle { + private let dictionary: [String: String] + + init(with dictionary: [String: String]) { + self.dictionary = dictionary + super.init() + } + + override func object(forInfoDictionaryKey key: String) -> Any? { + dictionary[key] + } +} From c1571d1081d116c68345044380762f4220d506e4 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 23 Sep 2022 15:37:03 +0100 Subject: [PATCH 25/30] Disable codecov/patch. --- changelog.d/pr-1579.build | 1 + codecov.yml | 9 +-------- 2 files changed, 2 insertions(+), 8 deletions(-) create mode 100644 changelog.d/pr-1579.build diff --git a/changelog.d/pr-1579.build b/changelog.d/pr-1579.build new file mode 100644 index 0000000000..f1b2d3642c --- /dev/null +++ b/changelog.d/pr-1579.build @@ -0,0 +1 @@ +Disable codecov/patch. diff --git a/codecov.yml b/codecov.yml index 33f6aec9f9..5fb3af8adf 100644 --- a/codecov.yml +++ b/codecov.yml @@ -6,11 +6,4 @@ coverage: # project coverage decrease by more than 1%: target: auto threshold: 1% - patch: - default: - # Be tolerant on slight code coverage diff on PRs to limit - # noisy red coverage status on github PRs. - # Note: The coverage stats are still uploaded - # to codecov so that PR reviewers can see uncovered lines - target: auto - threshold: 1% + patch: false From a7e46343b6685bbd9321307a2da728f10ac1fdcd Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Mon, 3 Oct 2022 13:42:26 +0200 Subject: [PATCH 26/30] Added support for MSC3881 --- .../Swift/JSONModels/MXJSONModels.swift | 12 +++++++ MatrixSDK/Contrib/Swift/MXRestClient.swift | 19 +++++++++-- MatrixSDK/JSONModels/MXMatrixVersions.h | 5 +++ MatrixSDK/JSONModels/MXMatrixVersions.m | 7 ++++ MatrixSDK/JSONModels/Push/MXPusher.h | 13 ++++++++ MatrixSDK/JSONModels/Push/MXPusher.m | 13 +++++++- MatrixSDK/MXRestClient.h | 33 ++++++++++++++++++- MatrixSDK/MXRestClient.m | 32 ++++++++++++++++-- changelog.d/6787.change | 1 + 9 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 changelog.d/6787.change diff --git a/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift b/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift index 76245d3661..615d163d31 100644 --- a/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift +++ b/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift @@ -56,6 +56,18 @@ public enum MXLoginFlowType: Equatable, Hashable { public enum MXPusherKind: Equatable, Hashable { case http, none, custom(String) + public static func from(value: String?) -> MXPusherKind { + guard let value = value else { + return .none + } + + if value == "http" { + return .http + } + + return .custom(value) + } + public var objectValue: NSObject { switch self { case .http: return "http" as NSString diff --git a/MatrixSDK/Contrib/Swift/MXRestClient.swift b/MatrixSDK/Contrib/Swift/MXRestClient.swift index 50ebcacc67..092fc91ab0 100644 --- a/MatrixSDK/Contrib/Swift/MXRestClient.swift +++ b/MatrixSDK/Contrib/Swift/MXRestClient.swift @@ -376,13 +376,15 @@ public extension MXRestClient { - profileTag: The profile tag for this device. Identifies this device in push rules. - lang: The user's preferred language for push, eg. 'en' or 'en-US' - data: Dictionary of data as required by your push gateway (generally the notification URI and aps-environment for APNS). + - append: If `true`, the homeserver should add another pusher with the given pushkey and App ID in addition to any others with different user IDs. + - enabled: Whether the pusher should actively create push notifications - completion: A block object called when the operation succeeds. - response: indicates whether the request succeeded or not. - returns: a `MXHTTPOperation` instance. */ - @nonobjc @discardableResult func setPusher(pushKey: String, kind: MXPusherKind, appId: String, appDisplayName: String, deviceDisplayName: String, profileTag: String, lang: String, data: [String: Any], append: Bool, completion: @escaping (_ response: MXResponse) -> Void) -> MXHTTPOperation { - return __setPusherWithPushkey(pushKey, kind: kind.objectValue, appId: appId, appDisplayName: appDisplayName, deviceDisplayName: deviceDisplayName, profileTag: profileTag, lang: lang, data: data, append: append, success: currySuccess(completion), failure: curryFailure(completion)) + @nonobjc @discardableResult func setPusher(pushKey: String, kind: MXPusherKind, appId: String, appDisplayName: String, deviceDisplayName: String, profileTag: String, lang: String, data: [String: Any], append: Bool, enabled: Bool = true, completion: @escaping (_ response: MXResponse) -> Void) -> MXHTTPOperation { + return __setPusherWithPushkey(pushKey, kind: kind.objectValue, appId: appId, appDisplayName: appDisplayName, deviceDisplayName: deviceDisplayName, profileTag: profileTag, lang: lang, data: data, append: append, enabled: enabled, success: currySuccess(completion), failure: curryFailure(completion)) } // TODO: setPusherWithPushKey - futher refinement /* @@ -392,7 +394,18 @@ public extension MXRestClient { Something like "MXPusherDescriptor"? */ - + /** + Gets all currently active pushers for the authenticated user. + + - parameters: + - response: indicates whether the request succeeded or not. + + - returns: a `MXHTTPOperation` instance. + */ + @nonobjc @discardableResult func pushers(completion: @escaping (_ response: MXResponse<[MXPusher]>) -> Void) -> MXHTTPOperation { + return __pushers(currySuccess(completion), failure: curryFailure(completion)) + } + /** Get all push notifications rules. diff --git a/MatrixSDK/JSONModels/MXMatrixVersions.h b/MatrixSDK/JSONModels/MXMatrixVersions.h index a31c8c513e..5ac9d89b35 100644 --- a/MatrixSDK/JSONModels/MXMatrixVersions.h +++ b/MatrixSDK/JSONModels/MXMatrixVersions.h @@ -98,6 +98,11 @@ extern const struct MXMatrixVersionsFeatureStruct MXMatrixVersionsFeature; */ @property (nonatomic, readonly) BOOL supportsThreads; +/** + Indicate if the server supports Remotely toggling push notifications via MSC3881. + */ +@property (nonatomic, readonly) BOOL supportsRemotelyTogglingPushNotifications; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/JSONModels/MXMatrixVersions.m b/MatrixSDK/JSONModels/MXMatrixVersions.m index 8ac4256c62..dc4d4b6815 100644 --- a/MatrixSDK/JSONModels/MXMatrixVersions.m +++ b/MatrixSDK/JSONModels/MXMatrixVersions.m @@ -42,6 +42,8 @@ // Unstable features static NSString* const kJSONKeyMSC3440 = @"org.matrix.msc3440.stable"; +static NSString* const kJSONKeyMSC3881 = @"org.matrix.msc3881"; +static NSString* const kJSONKeyMSC3881Stable = @"org.matrix.msc3881.stable"; @interface MXMatrixVersions () @@ -115,6 +117,11 @@ - (BOOL)supportsThreads return [self serverSupportsFeature:kJSONKeyMSC3440]; } +- (BOOL)supportsRemotelyTogglingPushNotifications +{ + return [self serverSupportsFeature:kJSONKeyMSC3881] || [self serverSupportsFeature:kJSONKeyMSC3881Stable]; +} + #pragma mark - Private - (BOOL)serverSupportsVersion:(NSString *)version diff --git a/MatrixSDK/JSONModels/Push/MXPusher.h b/MatrixSDK/JSONModels/Push/MXPusher.h index 1cacc5de93..51f3191f51 100644 --- a/MatrixSDK/JSONModels/Push/MXPusher.h +++ b/MatrixSDK/JSONModels/Push/MXPusher.h @@ -19,6 +19,9 @@ #import "MXJSONModel.h" #import "MXPusherData.h" +FOUNDATION_EXPORT NSString *const kMXPusherEnabledKey; +FOUNDATION_EXPORT NSString *const kMXPusherDeviceIdKey; + NS_ASSUME_NONNULL_BEGIN @interface MXPusher : MXJSONModel @@ -63,6 +66,16 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readonly) MXPusherData *data; +/** + Optional: Whether the pusher should actively create push notifications. default YES if `nil` + */ +@property (nonatomic, nullable, readonly) NSNumber *enabled; + +/** + Optional: The device_id of the session that registered the pusher. + */ +@property (nonatomic, nullable, readonly) NSString *deviceId; + @end NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/JSONModels/Push/MXPusher.m b/MatrixSDK/JSONModels/Push/MXPusher.m index 8d87ef9e40..e949423be5 100644 --- a/MatrixSDK/JSONModels/Push/MXPusher.m +++ b/MatrixSDK/JSONModels/Push/MXPusher.m @@ -16,13 +16,17 @@ #import "MXPusher.h" +NSString *const kMXPusherEnabledKey = @"org.matrix.msc3881.enabled"; +NSString *const kMXPusherDeviceIdKey = @"org.matrix.msc3881.device_id"; + @implementation MXPusher + (id)modelFromJSON:(NSDictionary *)JSONDictionary { MXPusher *pusher; - NSString *pushkey, *kind, *appId, *appDisplayName, *deviceDisplayName, *profileTag, *lang; + NSString *pushkey, *kind, *appId, *appDisplayName, *deviceDisplayName, *profileTag, *lang, *deviceId; + NSNumber *enabled; MXJSONModelSetString(pushkey, JSONDictionary[@"pushkey"]); MXJSONModelSetString(kind, JSONDictionary[@"kind"]); MXJSONModelSetString(appId, JSONDictionary[@"app_id"]); @@ -30,6 +34,11 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary MXJSONModelSetString(deviceDisplayName, JSONDictionary[@"device_display_name"]); MXJSONModelSetString(profileTag, JSONDictionary[@"profile_tag"]); MXJSONModelSetString(lang, JSONDictionary[@"lang"]); + if (JSONDictionary[kMXPusherEnabledKey]) + { + MXJSONModelSetNumber(enabled, JSONDictionary[kMXPusherEnabledKey]); + } + MXJSONModelSetString(deviceId, JSONDictionary[kMXPusherDeviceIdKey]); MXPusherData *data; MXJSONModelSetMXJSONModel(data, MXPusherData, JSONDictionary[@"data"]); @@ -45,6 +54,8 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary pusher->_profileTag = profileTag; pusher->_lang = lang; pusher->_data = data; + pusher->_enabled = enabled; + pusher->_deviceId = deviceId; } return pusher; diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index 9484620613..bf7dab4471 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -731,6 +731,7 @@ NS_REFINED_FOR_SWIFT; @param profileTag The profile tag for this device. Identifies this device in push rules. @param lang The user's preferred language for push, eg. 'en' or 'en-US' @param data Dictionary of data as required by your push gateway (generally the notification URI and aps-environment for APNS). + @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition to any others with different user IDs. @param success A block object called when the operation succeeds. It provides credentials to use to create a MXRestClient. @param failure A block object called when the operation fails. @@ -748,6 +749,36 @@ NS_REFINED_FOR_SWIFT; success:(void (^)(void))success failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; +/** + Update the pusher for this device on the Home Server. + + @param pushkey The pushkey for this pusher. This should be the APNS token formatted as required for your push gateway (base64 is the recommended formatting). + @param kind The kind of pusher your push gateway requires. Generally 'http', or an NSNull to disable the pusher. + @param appId The app ID of this application as required by your push gateway. + @param appDisplayName A human readable display name for this app. + @param deviceDisplayName A human readable display name for this device. + @param profileTag The profile tag for this device. Identifies this device in push rules. + @param lang The user's preferred language for push, eg. 'en' or 'en-US' + @param data Dictionary of data as required by your push gateway (generally the notification URI and aps-environment for APNS). + @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition to any others with different user IDs. + @param enabled Whether the pusher should actively create push notifications + @param success A block object called when the operation succeeds. It provides credentials to use to create a MXRestClient. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)setPusherWithPushkey:(NSString *)pushkey + kind:(NSObject *)kind + appId:(NSString *)appId + appDisplayName:(NSString *)appDisplayName + deviceDisplayName:(NSString *)deviceDisplayName + profileTag:(NSString *)profileTag + lang:(NSString *)lang + data:(NSDictionary *)data + append:(BOOL)append + enabled:(BOOL)enabled + success:(void (^)(void))success + failure:(void (^)(NSError *))failure NS_REFINED_FOR_SWIFT; /** Gets all currently active pushers for the authenticated user. @@ -758,7 +789,7 @@ NS_REFINED_FOR_SWIFT; @return a MXHTTPOperation instance. */ - (MXHTTPOperation*)pushers:(void (^)(NSArray *pushers))success - failure:(void (^)(NSError *))failure; + failure:(void (^)(NSError *))failure NS_REFINED_FOR_SWIFT; /** Get all push notifications rules. diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index b574ff4c20..dbf1b03654 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -1535,6 +1535,33 @@ - (MXHTTPOperation*)setPusherWithPushkey:(NSString *)pushkey append:(BOOL)append success:(void (^)(void))success failure:(void (^)(NSError *))failure +{ + return [self setPusherWithPushkey:pushkey + kind:kind + appId:appId + appDisplayName:appDisplayName + deviceDisplayName:deviceDisplayName + profileTag:profileTag + lang:lang + data:data + append:append + enabled:YES + success:success + failure:failure]; +} + +- (MXHTTPOperation*)setPusherWithPushkey:(NSString *)pushkey + kind:(NSObject *)kind + appId:(NSString *)appId + appDisplayName:(NSString *)appDisplayName + deviceDisplayName:(NSString *)deviceDisplayName + profileTag:(NSString *)profileTag + lang:(NSString *)lang + data:(NSDictionary *)data + append:(BOOL)append + enabled:(BOOL)enabled + success:(void (^)(void))success + failure:(void (^)(NSError *))failure { // sanity check if (!pushkey || !kind || !appDisplayName || !deviceDisplayName || !profileTag || !lang || !data) @@ -1558,6 +1585,7 @@ - (MXHTTPOperation*)setPusherWithPushkey:(NSString *)pushkey @"profile_tag": profileTag, @"lang": lang, @"data": data, + kMXPusherEnabledKey: @(enabled), @"append":[NSNumber numberWithBool:append] }; @@ -1591,7 +1619,7 @@ - (MXHTTPOperation*)pushers:(void (^)(NSArray *pushers))success [self dispatchProcessing:^{ MXJSONModelSetMXJSONModelArray(pushers, MXPusher, JSONResponse[@"pushers"]); } andCompletion:^{ - success(pushers); + success(pushers ?: @[]); }]; } } @@ -3200,7 +3228,7 @@ - (MXHTTPOperation*)roomSummaryWith:(NSString*)roomIdOrAlias success:(void (^)(MXPublicRoom *room))success failure:(void (^)(NSError *error))failure { - NSMutableString *path = [NSMutableString stringWithFormat:@"%@/im.nheko.summary/rooms/%@/summary", kMXAPIPrefixPathUnstable, roomIdOrAlias]; + NSMutableString *path = [NSMutableString stringWithFormat:@"%@/im.nheko.summary/rooms/%@/summary", kMXAPIPrefixPathUnstable, [MXTools encodeURIComponent:roomIdOrAlias]]; for (int i = 0; i < via.count; i++) { [path appendFormat:@"%@via=%@", i == 0 ? @"?" : @"&", via[i]]; } diff --git a/changelog.d/6787.change b/changelog.d/6787.change new file mode 100644 index 0000000000..b4defa9d7b --- /dev/null +++ b/changelog.d/6787.change @@ -0,0 +1 @@ +User sessions: Add support for MSC3881 From 570a7529b0ff1c54b592af61eefb601a88ee5d20 Mon Sep 17 00:00:00 2001 From: Gil Eluard Date: Mon, 3 Oct 2022 15:55:52 +0200 Subject: [PATCH 27/30] Added support for MSC3881 - Update after review --- MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift | 11 ++++------- MatrixSDK/JSONModels/MXMatrixVersions.m | 6 +++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift b/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift index 615d163d31..27654cdad4 100644 --- a/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift +++ b/MatrixSDK/Contrib/Swift/JSONModels/MXJSONModels.swift @@ -56,16 +56,13 @@ public enum MXLoginFlowType: Equatable, Hashable { public enum MXPusherKind: Equatable, Hashable { case http, none, custom(String) - public static func from(value: String?) -> MXPusherKind { + public init(value: String?) { guard let value = value else { - return .none - } - - if value == "http" { - return .http + self = .none + return } - return .custom(value) + self = value == "http" ? .http : .custom(value) } public var objectValue: NSObject { diff --git a/MatrixSDK/JSONModels/MXMatrixVersions.m b/MatrixSDK/JSONModels/MXMatrixVersions.m index dc4d4b6815..91bda10c18 100644 --- a/MatrixSDK/JSONModels/MXMatrixVersions.m +++ b/MatrixSDK/JSONModels/MXMatrixVersions.m @@ -42,8 +42,8 @@ // Unstable features static NSString* const kJSONKeyMSC3440 = @"org.matrix.msc3440.stable"; -static NSString* const kJSONKeyMSC3881 = @"org.matrix.msc3881"; -static NSString* const kJSONKeyMSC3881Stable = @"org.matrix.msc3881.stable"; +static NSString* const kJSONKeyMSC3881Unstable = @"org.matrix.msc3881"; +static NSString* const kJSONKeyMSC3881 = @"org.matrix.msc3881.stable"; @interface MXMatrixVersions () @@ -119,7 +119,7 @@ - (BOOL)supportsThreads - (BOOL)supportsRemotelyTogglingPushNotifications { - return [self serverSupportsFeature:kJSONKeyMSC3881] || [self serverSupportsFeature:kJSONKeyMSC3881Stable]; + return [self serverSupportsFeature:kJSONKeyMSC3881] || [self serverSupportsFeature:kJSONKeyMSC3881Unstable]; } #pragma mark - Private From 53b3455f0d10527df7876621c7a4edf1cbc2187b Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 11:27:17 +0100 Subject: [PATCH 28/30] version++ --- CHANGES.md | 24 ++++++++++++++++++++++++ MatrixSDK.podspec | 2 +- MatrixSDK/MatrixSDKVersion.m | 2 +- changelog.d/6670.change | 1 - changelog.d/6754.misc | 1 - changelog.d/6769.change | 1 - changelog.d/6773.change | 1 - changelog.d/6787.change | 1 - changelog.d/pr-1574.change | 1 - changelog.d/pr-1575.change | 1 - changelog.d/pr-1578.change | 1 - changelog.d/pr-1579.build | 1 - changelog.d/pr-1582.change | 1 - changelog.d/pr-1583.change | 1 - changelog.d/pr-1588.change | 1 - 15 files changed, 26 insertions(+), 14 deletions(-) delete mode 100644 changelog.d/6670.change delete mode 100644 changelog.d/6754.misc delete mode 100644 changelog.d/6769.change delete mode 100644 changelog.d/6773.change delete mode 100644 changelog.d/6787.change delete mode 100644 changelog.d/pr-1574.change delete mode 100644 changelog.d/pr-1575.change delete mode 100644 changelog.d/pr-1578.change delete mode 100644 changelog.d/pr-1579.build delete mode 100644 changelog.d/pr-1582.change delete mode 100644 changelog.d/pr-1583.change delete mode 100644 changelog.d/pr-1588.change diff --git a/CHANGES.md b/CHANGES.md index d18499778c..181a16ec88 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,27 @@ +## Changes in 0.24.0 (2022-10-04) + +🙌 Improvements + +- Upgrade minimum iOS and OSX deployment target to 13.0 and 10.15 respectively ([#1574](https://github.com/matrix-org/matrix-ios-sdk/pull/1574)) +- Crypto: Enable group session cache by default ([#1575](https://github.com/matrix-org/matrix-ios-sdk/pull/1575)) +- Crypto: Extract key backup engine ([#1578](https://github.com/matrix-org/matrix-ios-sdk/pull/1578)) +- MXSession: Set client information data if needed on resume. ([#1582](https://github.com/matrix-org/matrix-ios-sdk/pull/1582)) +- MXDevice: Move to dedicated file and implement MSC-3852. ([#1583](https://github.com/matrix-org/matrix-ios-sdk/pull/1583)) +- Add `enableNewClientInformationFeature` sdk option, disabled by default (PSG-799). ([#1588](https://github.com/matrix-org/matrix-ios-sdk/pull/1588)) +- Remove MXRoom's partialTextMessage support ([#6670](https://github.com/vector-im/element-ios/issues/6670)) +- CryptoV2: Key backups ([#6769](https://github.com/vector-im/element-ios/issues/6769)) +- CryptoV2: Key gossiping ([#6773](https://github.com/vector-im/element-ios/issues/6773)) +- User sessions: Add support for MSC3881 ([#6787](https://github.com/vector-im/element-ios/issues/6787)) + +🧱 Build + +- Disable codecov/patch. ([#1579](https://github.com/matrix-org/matrix-ios-sdk/pull/1579)) + +Others + +- Avoid main thread assertion if we can't get the application ([#6754](https://github.com/vector-im/element-ios/issues/6754)) + + ## Changes in 0.23.19 (2022-09-28) 🐛 Bugfixes diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index b002ee563c..c830d9fe5e 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "MatrixSDK" - s.version = "0.23.19" + s.version = "0.24.0" s.summary = "The iOS SDK to build apps compatible with Matrix (https://www.matrix.org)" s.description = <<-DESC diff --git a/MatrixSDK/MatrixSDKVersion.m b/MatrixSDK/MatrixSDKVersion.m index d716516621..09f6acd8da 100644 --- a/MatrixSDK/MatrixSDKVersion.m +++ b/MatrixSDK/MatrixSDKVersion.m @@ -16,4 +16,4 @@ #import -NSString *const MatrixSDKVersion = @"0.23.19"; +NSString *const MatrixSDKVersion = @"0.24.0"; diff --git a/changelog.d/6670.change b/changelog.d/6670.change deleted file mode 100644 index 75e436eddf..0000000000 --- a/changelog.d/6670.change +++ /dev/null @@ -1 +0,0 @@ -Remove MXRoom's partialTextMessage support diff --git a/changelog.d/6754.misc b/changelog.d/6754.misc deleted file mode 100644 index 2371dd3455..0000000000 --- a/changelog.d/6754.misc +++ /dev/null @@ -1 +0,0 @@ -Avoid main thread assertion if we can't get the application diff --git a/changelog.d/6769.change b/changelog.d/6769.change deleted file mode 100644 index 9821feb280..0000000000 --- a/changelog.d/6769.change +++ /dev/null @@ -1 +0,0 @@ -CryptoV2: Key backups diff --git a/changelog.d/6773.change b/changelog.d/6773.change deleted file mode 100644 index 670fcfdd03..0000000000 --- a/changelog.d/6773.change +++ /dev/null @@ -1 +0,0 @@ -CryptoV2: Key gossiping diff --git a/changelog.d/6787.change b/changelog.d/6787.change deleted file mode 100644 index b4defa9d7b..0000000000 --- a/changelog.d/6787.change +++ /dev/null @@ -1 +0,0 @@ -User sessions: Add support for MSC3881 diff --git a/changelog.d/pr-1574.change b/changelog.d/pr-1574.change deleted file mode 100644 index afcc67330f..0000000000 --- a/changelog.d/pr-1574.change +++ /dev/null @@ -1 +0,0 @@ -Upgrade minimum iOS and OSX deployment target to 13.0 and 10.15 respectively diff --git a/changelog.d/pr-1575.change b/changelog.d/pr-1575.change deleted file mode 100644 index 2f8ccbe89c..0000000000 --- a/changelog.d/pr-1575.change +++ /dev/null @@ -1 +0,0 @@ -Crypto: Enable group session cache by default diff --git a/changelog.d/pr-1578.change b/changelog.d/pr-1578.change deleted file mode 100644 index f467a04281..0000000000 --- a/changelog.d/pr-1578.change +++ /dev/null @@ -1 +0,0 @@ -Crypto: Extract key backup engine diff --git a/changelog.d/pr-1579.build b/changelog.d/pr-1579.build deleted file mode 100644 index f1b2d3642c..0000000000 --- a/changelog.d/pr-1579.build +++ /dev/null @@ -1 +0,0 @@ -Disable codecov/patch. diff --git a/changelog.d/pr-1582.change b/changelog.d/pr-1582.change deleted file mode 100644 index c12bdf90fb..0000000000 --- a/changelog.d/pr-1582.change +++ /dev/null @@ -1 +0,0 @@ -MXSession: Set client information data if needed on resume. diff --git a/changelog.d/pr-1583.change b/changelog.d/pr-1583.change deleted file mode 100644 index 3cf3bd72da..0000000000 --- a/changelog.d/pr-1583.change +++ /dev/null @@ -1 +0,0 @@ -MXDevice: Move to dedicated file and implement MSC-3852. diff --git a/changelog.d/pr-1588.change b/changelog.d/pr-1588.change deleted file mode 100644 index 4adf7484f5..0000000000 --- a/changelog.d/pr-1588.change +++ /dev/null @@ -1 +0,0 @@ -Add `enableNewClientInformationFeature` sdk option, disabled by default (PSG-799). From 3b85231a292ff76ea35789ec050e55d5b1c3dc6c Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 11:32:52 +0100 Subject: [PATCH 29/30] Move OS requirements to API changes in changelog. --- CHANGES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 181a16ec88..5d29b9f153 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,7 +2,6 @@ 🙌 Improvements -- Upgrade minimum iOS and OSX deployment target to 13.0 and 10.15 respectively ([#1574](https://github.com/matrix-org/matrix-ios-sdk/pull/1574)) - Crypto: Enable group session cache by default ([#1575](https://github.com/matrix-org/matrix-ios-sdk/pull/1575)) - Crypto: Extract key backup engine ([#1578](https://github.com/matrix-org/matrix-ios-sdk/pull/1578)) - MXSession: Set client information data if needed on resume. ([#1582](https://github.com/matrix-org/matrix-ios-sdk/pull/1582)) @@ -17,6 +16,10 @@ - Disable codecov/patch. ([#1579](https://github.com/matrix-org/matrix-ios-sdk/pull/1579)) +⚠️ API Changes + +- Upgrade minimum iOS and OSX deployment target to 13.0 and 10.15 respectively ([#1574](https://github.com/matrix-org/matrix-ios-sdk/pull/1574)) + Others - Avoid main thread assertion if we can't get the application ([#6754](https://github.com/vector-im/element-ios/issues/6754)) From f7236b26401720a320782b187e7928b2876062be Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 4 Oct 2022 12:12:22 +0100 Subject: [PATCH 30/30] finish version++