diff --git a/.github/workflows/ci-integration-tests.yml b/.github/workflows/ci-integration-tests.yml index 694840f7f3..81fd1e95a4 100644 --- a/.github/workflows/ci-integration-tests.yml +++ b/.github/workflows/ci-integration-tests.yml @@ -12,7 +12,7 @@ on: jobs: integration-tests: name: Integration Tests - runs-on: macos-11 + runs-on: macos-13 concurrency: # When running on develop, use the sha to allow all runs of this workflow to run concurrently. diff --git a/.github/workflows/ci-lint.yml b/.github/workflows/ci-lint.yml index 4b346189dc..458fd030e6 100644 --- a/.github/workflows/ci-lint.yml +++ b/.github/workflows/ci-lint.yml @@ -12,7 +12,7 @@ on: jobs: lint: name: pod lib lint - runs-on: macos-11 + runs-on: macos-13 concurrency: # When running on develop, use the sha to allow all runs of this workflow to run concurrently. diff --git a/.github/workflows/ci-unit-tests.yml b/.github/workflows/ci-unit-tests.yml index ad5395fea7..90a46ffb3d 100644 --- a/.github/workflows/ci-unit-tests.yml +++ b/.github/workflows/ci-unit-tests.yml @@ -12,7 +12,7 @@ on: jobs: unit-tests: name: Unit Tests - runs-on: macos-11 + runs-on: macos-13 concurrency: # When running on develop, use the sha to allow all runs of this workflow to run concurrently. diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 3455e2bbc5..59ab296d94 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -10,7 +10,7 @@ jobs: unit-tests: name: Unit Tests with sanitizer checks - runs-on: macos-11 + runs-on: macos-13 steps: - uses: actions/checkout@v2 diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index 5080455d7b..8462e3778f 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -45,7 +45,7 @@ Pod::Spec.new do |s| ss.dependency 'OLMKit', '~> 3.2.5' ss.dependency 'Realm', '10.27.0' ss.dependency 'libbase58', '~> 0.1.4' - ss.dependency 'MatrixSDKCrypto', '0.3.11', :configurations => ["DEBUG", "RELEASE"], :inhibit_warnings => true + ss.dependency 'MatrixSDKCrypto', '0.3.12', :configurations => ["DEBUG", "RELEASE"], :inhibit_warnings => true end s.subspec 'JingleCallStack' do |ss| diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 643661e11e..e2df01a296 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -43,6 +43,8 @@ 1838928927031D1D003F0C4F /* MXSendReplyEventStringLocalizerProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1838928527031D1D003F0C4F /* MXSendReplyEventStringLocalizerProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18937E7C273A5AE500902626 /* MXPollRelationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 18937E79273A5AB000902626 /* MXPollRelationTests.m */; }; 18937E7D273A5AE500902626 /* MXPollRelationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 18937E79273A5AB000902626 /* MXPollRelationTests.m */; }; + 189B8D102A864C250088D7CE /* DehydrationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189B8D0F2A864C250088D7CE /* DehydrationService.swift */; }; + 189B8D112A864C250088D7CE /* DehydrationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189B8D0F2A864C250088D7CE /* DehydrationService.swift */; }; 18B22A7027707CDD00482170 /* MXEventContentLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 18B22A6E27707CDD00482170 /* MXEventContentLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18B22A7127707CDD00482170 /* MXEventContentLocation.h in Headers */ = {isa = PBXBuildFile; fileRef = 18B22A6E27707CDD00482170 /* MXEventContentLocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; 18B22A7227707CDD00482170 /* MXEventContentLocation.m in Sources */ = {isa = PBXBuildFile; fileRef = 18B22A6F27707CDD00482170 /* MXEventContentLocation.m */; }; @@ -680,8 +682,6 @@ 3A59A52C25A7B1B000DDA1FC /* MXOutboundSessionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A59A52825A7B1B000DDA1FC /* MXOutboundSessionInfo.m */; }; 3A7509BB26FC61DF00B85773 /* MXSpaceNotificationCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7509BA26FC61DF00B85773 /* MXSpaceNotificationCounter.swift */; }; 3A7509BC26FC61DF00B85773 /* MXSpaceNotificationCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A7509BA26FC61DF00B85773 /* MXSpaceNotificationCounter.swift */; }; - 3A7B8D0E267FCF7200D9DD96 /* MXDehydrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A7B8CFD267FCD9B00D9DD96 /* MXDehydrationTests.m */; }; - 3A7B8D13267FCF7300D9DD96 /* MXDehydrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A7B8CFD267FCD9B00D9DD96 /* MXDehydrationTests.m */; }; 3A858DDA2750EE3F006322C1 /* MXHomeserverCapabilitiesService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A858DD92750EE3F006322C1 /* MXHomeserverCapabilitiesService.swift */; }; 3A858DDD275121DB006322C1 /* MXHomeserverCapabilitiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A858DDB275120D1006322C1 /* MXHomeserverCapabilitiesTests.swift */; }; 3A858DDE275121DC006322C1 /* MXHomeserverCapabilitiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A858DDB275120D1006322C1 /* MXHomeserverCapabilitiesTests.swift */; }; @@ -698,10 +698,6 @@ 3AB5EBB5270B332B0058703A /* MXSpaceStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB5EBB3270B332B0058703A /* MXSpaceStore.swift */; }; 3AB5EBB7270ED1C00058703A /* MXSpaceFileStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB5EBB6270ED1C00058703A /* MXSpaceFileStore.swift */; }; 3AB5EBB8270ED1C00058703A /* MXSpaceFileStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB5EBB6270ED1C00058703A /* MXSpaceFileStore.swift */; }; - 3AC135D92640335100EE1E74 /* MXDehydrationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AC135D72640335100EE1E74 /* MXDehydrationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3AC135DA2640335100EE1E74 /* MXDehydrationService.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AC135D72640335100EE1E74 /* MXDehydrationService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3AC135DB2640335100EE1E74 /* MXDehydrationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AC135D82640335100EE1E74 /* MXDehydrationService.m */; }; - 3AC135DC2640335100EE1E74 /* MXDehydrationService.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AC135D82640335100EE1E74 /* MXDehydrationService.m */; }; 3AC13802264482A100EE1E74 /* MXExportedOlmDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AC13800264482A100EE1E74 /* MXExportedOlmDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3AC13803264482A100EE1E74 /* MXExportedOlmDevice.h in Headers */ = {isa = PBXBuildFile; fileRef = 3AC13800264482A100EE1E74 /* MXExportedOlmDevice.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3AC13804264482A100EE1E74 /* MXExportedOlmDevice.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AC13801264482A100EE1E74 /* MXExportedOlmDevice.m */; }; @@ -2166,6 +2162,7 @@ 1838928427031D1D003F0C4F /* MXRoomNameStringLocalizerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXRoomNameStringLocalizerProtocol.h; sourceTree = ""; }; 1838928527031D1D003F0C4F /* MXSendReplyEventStringLocalizerProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXSendReplyEventStringLocalizerProtocol.h; sourceTree = ""; }; 18937E79273A5AB000902626 /* MXPollRelationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXPollRelationTests.m; sourceTree = ""; }; + 189B8D0F2A864C250088D7CE /* DehydrationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DehydrationService.swift; sourceTree = ""; }; 18B22A6E27707CDD00482170 /* MXEventContentLocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXEventContentLocation.h; sourceTree = ""; }; 18B22A6F27707CDD00482170 /* MXEventContentLocation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXEventContentLocation.m; sourceTree = ""; }; 18C26C3C273C031900805154 /* PollAggregator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollAggregator.swift; sourceTree = ""; }; @@ -2670,7 +2667,6 @@ 3A59A52725A7B1B000DDA1FC /* MXOutboundSessionInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXOutboundSessionInfo.h; sourceTree = ""; }; 3A59A52825A7B1B000DDA1FC /* MXOutboundSessionInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXOutboundSessionInfo.m; sourceTree = ""; }; 3A7509BA26FC61DF00B85773 /* MXSpaceNotificationCounter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSpaceNotificationCounter.swift; sourceTree = ""; }; - 3A7B8CFD267FCD9B00D9DD96 /* MXDehydrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXDehydrationTests.m; sourceTree = ""; }; 3A858DD92750EE3F006322C1 /* MXHomeserverCapabilitiesService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXHomeserverCapabilitiesService.swift; sourceTree = ""; }; 3A858DDB275120D1006322C1 /* MXHomeserverCapabilitiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXHomeserverCapabilitiesTests.swift; sourceTree = ""; }; 3A858DE027517C0E006322C1 /* MXRoomCapabilityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXRoomCapabilityType.swift; sourceTree = ""; }; @@ -2679,8 +2675,6 @@ 3A9E2B4228EB3960000DB2A7 /* MXMatrixVersionsUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXMatrixVersionsUnitTests.swift; sourceTree = ""; }; 3AB5EBB3270B332B0058703A /* MXSpaceStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSpaceStore.swift; sourceTree = ""; }; 3AB5EBB6270ED1C00058703A /* MXSpaceFileStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MXSpaceFileStore.swift; sourceTree = ""; }; - 3AC135D72640335100EE1E74 /* MXDehydrationService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXDehydrationService.h; sourceTree = ""; }; - 3AC135D82640335100EE1E74 /* MXDehydrationService.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXDehydrationService.m; sourceTree = ""; }; 3AC13800264482A100EE1E74 /* MXExportedOlmDevice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXExportedOlmDevice.h; sourceTree = ""; }; 3AC13801264482A100EE1E74 /* MXExportedOlmDevice.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXExportedOlmDevice.m; sourceTree = ""; }; 3AD4F230274B922C003F47FE /* MXRoomAliasAvailabilityChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRoomAliasAvailabilityChecker.swift; sourceTree = ""; }; @@ -4389,7 +4383,6 @@ 324DD2BA246C3ADE00377005 /* MXCryptoSecretStorageTests.m */, 322D01C322492B0700150C68 /* MXCryptoShareTests.m */, 322A51D71D9E846800C8536D /* MXCryptoTests.m */, - 3A7B8CFD267FCD9B00D9DD96 /* MXDehydrationTests.m */, 3281E89D19E299C000976E1A /* MXErrorUnitTests.m */, B146D4FC21A5C0BC00D8C2C6 /* MXEventScanStoreUnitTests.m */, 32A31BC020D3F4C4005916C7 /* MXFilterTests.m */, @@ -4656,8 +4649,7 @@ 3AC1379326432ED500EE1E74 /* Dehydration */ = { isa = PBXGroup; children = ( - 3AC135D72640335100EE1E74 /* MXDehydrationService.h */, - 3AC135D82640335100EE1E74 /* MXDehydrationService.m */, + 189B8D0F2A864C250088D7CE /* DehydrationService.swift */, ); path = Dehydration; sourceTree = ""; @@ -6047,7 +6039,6 @@ 18B22A7027707CDD00482170 /* MXEventContentLocation.h in Headers */, EC60EDC6265CFEA800B39A4E /* MXRoomSyncUnreadNotifications.h in Headers */, 329FB1791A0A74B100A5E88E /* MXTools.h in Headers */, - 3AC135D92640335100EE1E74 /* MXDehydrationService.h in Headers */, 322691321E5EF77D00966A6E /* MXDeviceListOperation.h in Headers */, EC60EE06265CFFF400B39A4E /* MXGroupSyncProfile.h in Headers */, 32481A841C03572900782AD3 /* MXRoomAccountData.h in Headers */, @@ -6550,7 +6541,6 @@ B19A309F240424BD00FB6F35 /* MXQRCodeTransaction_Private.h in Headers */, 3AC13803264482A100EE1E74 /* MXExportedOlmDevice.h in Headers */, B14EF3262397E90400758AF0 /* MXEventScanStoreDelegate.h in Headers */, - 3AC135DA2640335100EE1E74 /* MXDehydrationService.h in Headers */, B14EF3292397E90400758AF0 /* MXAutoDiscovery.h in Headers */, EC8A53D725B1BCC6004E0802 /* MXThirdPartyUserInstance.h in Headers */, EC60EDD1265CFECC00B39A4E /* MXRoomSyncSummary.h in Headers */, @@ -6942,6 +6932,7 @@ 32A1513F1DAF768D00400192 /* MXOlmInboundGroupSession.m in Sources */, 3259CFE626026A6F00C365DB /* MXRestClient+Extensions.swift in Sources */, EC60ED91265CFD3B00B39A4E /* MXRoomSync.m in Sources */, + 189B8D102A864C250088D7CE /* DehydrationService.swift in Sources */, 3AB5EBB4270B332B0058703A /* MXSpaceStore.swift in Sources */, EC8A53A125B1BC77004E0802 /* MXCallSelectAnswerEventContent.m in Sources */, B14EECE72577F76100448735 /* MXLoginSSOFlow.m in Sources */, @@ -6997,7 +6988,6 @@ EC60ED87265CFD0700B39A4E /* MXRoomsSyncResponse.m in Sources */, 3213301E228B190F0070BA9B /* MXRealmAggregationsMapper.m in Sources */, 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 */, @@ -7523,7 +7513,6 @@ ECAE7AEC24ED75F1002FA813 /* MXHTTPAdditionalHeadersUnitTests.m in Sources */, 32AF9292241112850008A0FD /* MXCryptoSecretShareTests.m in Sources */, ED8F1D342885ADE200F897E7 /* MXCryptoProtocolStubs.swift in Sources */, - 3A7B8D0E267FCF7200D9DD96 /* MXDehydrationTests.m in Sources */, 3281E8A019E2CC1200976E1A /* MXHTTPClientTests.m in Sources */, 321809B919EEBF3000377451 /* MXEventTests.m in Sources */, B1B44319283D00CA00BB26F4 /* MXMegolmDecryptionUnitTests.swift in Sources */, @@ -7614,6 +7603,7 @@ B19EC8A3260E134A00543BEC /* MXRoomInitialStateEventBuilder.swift in Sources */, B14EF1CA2397E90400758AF0 /* MXOlmInboundGroupSession.m in Sources */, B14EF1CB2397E90400758AF0 /* MXRoomAccountData.m in Sources */, + 189B8D112A864C250088D7CE /* DehydrationService.swift in Sources */, 3AB5EBB5270B332B0058703A /* MXSpaceStore.swift in Sources */, B14EF1CC2397E90400758AF0 /* MXEventAnnotationChunk.m in Sources */, B14EF1CD2397E90400758AF0 /* MXRealmEventScanStore.m in Sources */, @@ -8034,7 +8024,6 @@ B1EE98D22804883B00AB63F0 /* MXGeoURIComponents.swift in Sources */, 323F877E25546170009E9E67 /* MXBaseProfiler.m in Sources */, ECDA763627B527BA000C48CF /* MXEvent+Extensions.swift in Sources */, - 3AC135DC2640335100EE1E74 /* MXDehydrationService.m in Sources */, B141DF0F283CDD180023867A /* Realm+MatrixSDK.swift in Sources */, B14EF27C2397E90400758AF0 /* MXRestClient.swift in Sources */, 32AF929A24115D8B0008A0FD /* MXPendingSecretShareRequest.m in Sources */, @@ -8193,7 +8182,6 @@ B1E09A292397FD080057C069 /* MXLoggerUnitTests.m in Sources */, B1E09A492398028D0057C069 /* MXSelfSignedHomeserverTests.m in Sources */, 32B0E3E523A384D40054FF1A /* MXAggregatedReferenceTests.m in Sources */, - 3A7B8D13267FCF7300D9DD96 /* MXDehydrationTests.m in Sources */, ED7019E32886C29400FC31B9 /* Device+Stub.swift in Sources */, 32D5D16423E400A600E3E37C /* MXRoomSummaryTrustTests.m in Sources */, B1E09A1B2397FCE90057C069 /* MXAggregatedReactionTests.m in Sources */, diff --git a/MatrixSDK/Background/Crypto/MXBackgroundCryptoV2.swift b/MatrixSDK/Background/Crypto/MXBackgroundCryptoV2.swift index d2db3ede3f..a785fb0168 100644 --- a/MatrixSDK/Background/Crypto/MXBackgroundCryptoV2.swift +++ b/MatrixSDK/Background/Crypto/MXBackgroundCryptoV2.swift @@ -53,7 +53,8 @@ class MXBackgroundCryptoV2: MXBackgroundCrypto { toDevice: syncResponse.toDevice, deviceLists: syncResponse.deviceLists, deviceOneTimeKeysCounts: syncResponse.deviceOneTimeKeysCount ?? [:], - unusedFallbackKeys: syncResponse.unusedFallbackKeys + unusedFallbackKeys: syncResponse.unusedFallbackKeys, + nextBatchToken: syncResponse.nextBatch ) } catch { log.error("Failed handling sync response", context: error) diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift index faef0c2c9e..da4f1ff2f6 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoMachine.swift @@ -187,7 +187,8 @@ extension MXCryptoMachine: MXCryptoSyncing { toDevice: MXToDeviceSyncResponse?, deviceLists: MXDeviceListResponse?, deviceOneTimeKeysCounts: [String: NSNumber], - unusedFallbackKeys: [String]? + unusedFallbackKeys: [String]?, + nextBatchToken: String ) throws -> MXToDeviceSyncResponse { let events = toDevice?.jsonString() ?? "[]" let deviceChanges = DeviceLists( @@ -200,7 +201,8 @@ extension MXCryptoMachine: MXCryptoSyncing { events: events, deviceChanges: deviceChanges, keyCounts: keyCounts, - unusedFallbackKeys: unusedFallbackKeys + unusedFallbackKeys: unusedFallbackKeys, + nextBatchToken: nextBatchToken ) var deserialisedToDeviceEvents = [Any]() @@ -367,6 +369,10 @@ extension MXCryptoMachine: MXCryptoDevicesSource { return nil } } + + func dehydratedDevices() -> DehydratedDevicesProtocol { + machine.dehydratedDevices() + } } extension MXCryptoMachine: MXCryptoUserIdentitySource { diff --git a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift index 65dee0f900..2a53e44f4f 100644 --- a/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift +++ b/MatrixSDK/Crypto/CryptoMachine/MXCryptoProtocols.swift @@ -35,7 +35,8 @@ protocol MXCryptoSyncing: MXCryptoIdentity { toDevice: MXToDeviceSyncResponse?, deviceLists: MXDeviceListResponse?, deviceOneTimeKeysCounts: [String: NSNumber], - unusedFallbackKeys: [String]? + unusedFallbackKeys: [String]?, + nextBatchToken: String ) throws -> MXToDeviceSyncResponse func processOutgoingRequests() async throws @@ -50,6 +51,7 @@ protocol MXCryptoSyncing: MXCryptoIdentity { protocol MXCryptoDevicesSource: MXCryptoIdentity { func device(userId: String, deviceId: String) -> Device? func devices(userId: String) -> [Device] + func dehydratedDevices() -> DehydratedDevicesProtocol } /// Source of user identities and their cryptographic trust status diff --git a/MatrixSDK/Crypto/Dehydration/DehydrationService.swift b/MatrixSDK/Crypto/Dehydration/DehydrationService.swift new file mode 100644 index 0000000000..6f4ccb9c77 --- /dev/null +++ b/MatrixSDK/Crypto/Dehydration/DehydrationService.swift @@ -0,0 +1,219 @@ +// +// Copyright 2023 The Matrix.org Foundation C.I.C +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDKCrypto + +enum DehydrationServiceError: Error { + case failedDehydration(Error) + case noDehydratedDeviceAvailable(Error) + case failedRehydration(Error) + case invalidRehydratedDeviceData + case failedStoringSecret(Error) + case failedRetrievingSecret(Error) + case failedRetrievingPrivateKey(Error) + case invalidSecretStorageDefaultKeyId + case failedRetrievingToDeviceEvents(Error) + case failedDeletingDehydratedDevice(Error) +} + +@objcMembers +public class DehydrationService: NSObject { + let deviceDisplayName = "Backup Device" + let restClient: MXRestClient + let secretStorage: MXSecretStorage + let dehydratedDevices: DehydratedDevicesProtocol + + + init(restClient: MXRestClient, secretStorage: MXSecretStorage, dehydratedDevices: DehydratedDevicesProtocol) { + self.restClient = restClient + self.secretStorage = secretStorage + self.dehydratedDevices = dehydratedDevices + } + + public func runDeviceDehydrationFlow(privateKeyData: Data) async { + do { + try await _runDeviceDehydrationFlow(privateKeyData: privateKeyData) + } catch { + MXLog.error("Failed device dehydration flow", context: error) + } + } + + private func _runDeviceDehydrationFlow(privateKeyData: Data) async throws { + guard let secretStorageKeyId = self.secretStorage.defaultKeyId() else { + throw DehydrationServiceError.invalidSecretStorageDefaultKeyId + } + + let secretId = MXSecretId.dehydratedDevice.takeUnretainedValue() as String + + // If we have a dehydration pickle key stored on the backend, use it to rehydrate a device, then process + // that device's events and then create a new dehydrated device + if secretStorage.hasSecret(withSecretId: secretId, withSecretStorageKeyId: secretStorageKeyId) { + // If available, retrieve the base64 encoded pickle key from the backend + let base64PickleKey = try await retrieveSecret(forSecretId: secretId, secretStorageKey: secretStorageKeyId, privateKeyData: privateKeyData) + + // Convert it back to Data + let pickleKeyData = MXBase64Tools.data(fromBase64: base64PickleKey) + + let rehydrationResult = await rehydrateDevice(pickleKeyData: [UInt8](pickleKeyData)) + switch rehydrationResult { + case .success((let deviceId, let rehydratedDevice)): + // Fetch and process the to device events available on the dehydrated device + try await processToDeviceEvents(rehydratedDevice: rehydratedDevice, deviceId: deviceId) + + // And attempt to delete the dehydrated device but ignore failures + try? await deleteDehydratedDevice(deviceId: deviceId) + case .failure(let error): + // If no dehydrated devices are available just continue and create a new one + if case .noDehydratedDeviceAvailable = error { + break + } else { + throw error + } + } + + // Finally, create a new dehydrated device with the same pickle key + try await dehydrateDevice(pickleKeyData: [UInt8](pickleKeyData)) + } else { // Otherwise, generate a new dehydration pickle key, store it and dehydrate a device + // Generate a new dehydration pickle key + var pickleKeyData = [UInt8](repeating: 0, count: 32) + _ = SecRandomCopyBytes(kSecRandomDefault, 32, &pickleKeyData) + + // Convert it to unpadded base 64 + let base64PickleKey = MXBase64Tools.unpaddedBase64(from: Data(bytes: pickleKeyData, count: 32)) + + // Store it on the backend + try await storeSecret(base64PickleKey, secretId: secretId, secretStorageKeys: [secretStorageKeyId: privateKeyData]) + + // Dehydrate a new device using the new pickle key + try await dehydrateDevice(pickleKeyData: pickleKeyData) + } + } + + // MARK: - Secret storage + + private func storeSecret(_ unpaddedBase64Secret: String, secretId: String, secretStorageKeys: [String: Data]) async throws { + try await withCheckedThrowingContinuation { continuation in + self.secretStorage.storeSecret(unpaddedBase64Secret, withSecretId: secretId, withSecretStorageKeys: secretStorageKeys) { secretId in + MXLog.info("Stored secret with secret id: \(secretId)") + continuation.resume() + } failure: { error in + MXLog.error("Failed storing secret", context: error) + continuation.resume(throwing: DehydrationServiceError.failedStoringSecret(error)) + } + } + } + + private func retrieveSecret(forSecretId secretId: String, secretStorageKey: String, privateKeyData: Data) async throws -> String { + try await withCheckedThrowingContinuation { continuation in + self.secretStorage.secret(withSecretId: secretId, withSecretStorageKeyId: secretStorageKey, privateKey: privateKeyData) { secret in + MXLog.info("Retrieved secret with secret id: \(secretId)") + continuation.resume(returning: secret) + } failure: { error in + MXLog.error("Failed retrieving secret", context: error) + continuation.resume(throwing: DehydrationServiceError.failedRetrievingSecret(error)) + } + } + } + + // MARK: - Device dehydration + + private func dehydrateDevice(pickleKeyData: [UInt8]) async throws { + let dehydratedDevice = dehydratedDevices.create() + + let requestDetails = try dehydratedDevice.keysForUpload(deviceDisplayName: deviceDisplayName, pickleKey: [UInt8](pickleKeyData)) + + let parameters = MXDehydratedDeviceCreationParameters() + parameters.body = requestDetails.body + + return try await withCheckedThrowingContinuation { continuation in + restClient.createDehydratedDevice(parameters) { deviceId in + MXLog.info("Successfully created dehydrated device with id: \(deviceId)") + continuation.resume() + } failure: { error in + MXLog.error("Failed creating dehydrated device", context: error) + continuation.resume(throwing: DehydrationServiceError.failedDehydration(error)) + } + } + } + + private func rehydrateDevice(pickleKeyData: [UInt8]) async -> Result<(deviceId: String, rehydratedDevice: RehydratedDeviceProtocol), DehydrationServiceError> { + await withCheckedContinuation { continuation in + self.restClient.retrieveDehydratedDevice { [weak self] dehydratedDevice in + guard let self else { return } + + MXLog.info("Successfully retrieved dehydrated device with id: \(dehydratedDevice.deviceId)") + + guard let deviceDataJSON = MXTools.serialiseJSONObject(dehydratedDevice.deviceData) else { + continuation.resume(returning: .failure(DehydrationServiceError.invalidRehydratedDeviceData)) + return + } + + do { + let rehydratedDevice = try self.dehydratedDevices.rehydrate(pickleKey: [UInt8](pickleKeyData), deviceId: dehydratedDevice.deviceId, deviceData: deviceDataJSON) + continuation.resume(returning: .success((dehydratedDevice.deviceId, rehydratedDevice))) + } catch { + continuation.resume(returning: .failure(DehydrationServiceError.failedRehydration(error))) + } + } failure: { error in + MXLog.error("Failed retrieving dehidrated device", context: error) + if let mxError = MXError(nsError: error), + mxError.errcode == kMXErrCodeStringNotFound { + continuation.resume(returning: .failure(DehydrationServiceError.noDehydratedDeviceAvailable(error))) + } else { + continuation.resume(returning: .failure(DehydrationServiceError.failedRehydration(error))) + } + } + } + } + + private func deleteDehydratedDevice(deviceId: String) async throws { + try await withCheckedThrowingContinuation { continuation in + restClient.deleteDehydratedDevice { + MXLog.info("Deleted dehydrated device with id: \(deviceId)") + continuation.resume() + } failure: { error in + MXLog.error("Failed retrieving dehydrated device events", context: error) + continuation.resume(throwing: DehydrationServiceError.failedRetrievingPrivateKey(error)) + } + } + } + + // MARK: - To device event processing + + private func processToDeviceEvents(rehydratedDevice: RehydratedDeviceProtocol, deviceId: String) async throws { + var dehydratedDeviceEventsResponse: MXDehydratedDeviceEventsResponse? + + repeat { + let response = try await retrieveToDeviceEvents(deviceId: deviceId, nextBatch: dehydratedDeviceEventsResponse?.nextBatch) + try rehydratedDevice.receiveEvents(events: MXTools.serialiseJSONObject(response.events)) + dehydratedDeviceEventsResponse = response + + } while !(dehydratedDeviceEventsResponse?.events.isEmpty ?? true) + } + + private func retrieveToDeviceEvents(deviceId: String, nextBatch: String?) async throws -> MXDehydratedDeviceEventsResponse { + try await withCheckedThrowingContinuation { continuation in + restClient.retrieveDehydratedDeviceEvents(forDeviceId: deviceId, nextBatch: nextBatch) { dehydratedDeviceEventsResponse in + MXLog.info("Retrieved dehydrated device events for device id: \(deviceId)") + continuation.resume(returning: dehydratedDeviceEventsResponse) + } failure: { error in + MXLog.error("Failed deleting dehydrated device", context: error) + continuation.resume(throwing: DehydrationServiceError.failedDeletingDehydratedDevice(error)) + } + } + } +} diff --git a/MatrixSDK/Crypto/Dehydration/MXDehydrationService.h b/MatrixSDK/Crypto/Dehydration/MXDehydrationService.h deleted file mode 100644 index cd7d6f5983..0000000000 --- a/MatrixSDK/Crypto/Dehydration/MXDehydrationService.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright 2021 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 - -@class MXLegacyCrossSigning; -@class MXRestClient; -@class MXExportedOlmDevice; - -NS_ASSUME_NONNULL_BEGIN - -/// Error domain for this class. -FOUNDATION_EXPORT NSString *const MXDehydrationServiceErrorDomain; - -typedef NS_ENUM(NSInteger, MXDehydrationServiceErrorCode) -{ - MXDehydrationServiceAlreadyRuningErrorCode, - MXDehydrationServiceNothingToRehydrateErrorCode, - MXDehydrationServiceAlreadyClaimedErrorCode, -}; - -/** - Service in charge of dehydrating and rehydrating a device. - - @see https://github.com/uhoreg/matrix-doc/blob/dehydration/proposals/2697-device-dehydration.md for more details - */ -@interface MXDehydrationService : NSObject - -/** - Dehydrate a new device for the current account - - @param restClient client used to call the dehydration API - @param crossSigning cross signing used to self sign the dehydrated device - @param dehydrationKey key used to pickle the Olm account - @param success callback called in case of success - @param failure callback called in case of unexpected failure - */ -- (void)dehydrateDeviceWithMatrixRestClient:(MXRestClient*)restClient - crossSigning:(MXLegacyCrossSigning *)crossSigning - dehydrationKey:(NSData*)dehydrationKey - success:(void (^)(NSString * deviceId))success - failure:(void (^)(NSError *error))failure; - -/** - Rehydrate the dehydrated device of the current acount - - @param restClient client used to call the dehydration API - @param dehydrationKey key used to unpickle the Olm account - @param success callback called in case of success - @param failure callback called in case of unexpected failure - */ -- (void)rehydrateDeviceWithMatrixRestClient:(MXRestClient*)restClient - dehydrationKey:(NSData*)dehydrationKey - success:(void (^)(NSString * deviceId))success - failure:(void (^)(NSError *error))failure; - -@end - -NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/Dehydration/MXDehydrationService.m b/MatrixSDK/Crypto/Dehydration/MXDehydrationService.m deleted file mode 100644 index 7b440b2a8d..0000000000 --- a/MatrixSDK/Crypto/Dehydration/MXDehydrationService.m +++ /dev/null @@ -1,282 +0,0 @@ -// -// Copyright 2021 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 "MXDehydrationService.h" -#import -#import "MXCrypto.h" -#import "MXCrypto_private.h" -#import "MXCryptoTools.h" -#import "MXKeyProvider.h" -#import "MXCrossSigning_Private.h" -#import "MXRawDataKey.h" -#import "MXSession.h" -#import "MXKey.h" - -NSString *const MXDehydrationAlgorithm = @"org.matrix.msc2697.v1.olm.libolm_pickle"; -NSString *const MXDehydrationServiceErrorDomain = @"org.matrix.sdk.dehydration.service"; - -@interface MXDehydrationService () - -@property (nonatomic, assign) BOOL inProgress; - -@end - -@implementation MXDehydrationService - -- (void)dehydrateDeviceWithMatrixRestClient:(MXRestClient*)restClient - crossSigning:(MXLegacyCrossSigning *)crossSigning - dehydrationKey:(NSData*)dehydrationKey - success:(void (^)( NSString * deviceId))success - failure:(void (^)(NSError *error))failure; -{ - if (self.inProgress) - { - MXLogDebug(@"[MXDehydrationService] dehydrateDevice: Dehydration already in progress -- not starting new dehydration"); - NSError *error = [NSError errorWithDomain:MXDehydrationServiceErrorDomain - code:MXDehydrationServiceAlreadyRuningErrorCode - userInfo:@{ - NSLocalizedDescriptionKey: @"Dehydration already in progress -- not starting new dehydration", - }]; - failure(error); - return; - } - - self.inProgress = YES; - - OLMAccount *account = [[OLMAccount alloc] initNewAccount]; - NSDictionary *e2eKeys = [account identityKeys]; - - NSUInteger maxKeys = [account maxOneTimeKeys]; - [account generateOneTimeKeys:maxKeys / 2]; - - [account generateFallbackKey]; - - MXLogDebug(@"[MXDehydrationService] dehydrateDevice: Account created %@", account.identityKeys); - - // Dehydrate the account and store it into the server - NSError *error = nil; - MXDehydratedDevice *dehydratedDevice = [MXDehydratedDevice new]; - dehydratedDevice.account = [account serializeDataWithKey:dehydrationKey error:&error]; - dehydratedDevice.algorithm = MXDehydrationAlgorithm; - - if (error) - { - MXLogErrorDetails(@"[MXDehydrationService] dehydrateDevice: Account serialization failed", @{ - @"error": error ?: @"unknown" - }); - [self stopProgress]; - failure(error); - return; - } - - MXWeakify(restClient); - MXWeakify(self); - [restClient setDehydratedDevice:dehydratedDevice withDisplayName:@"Backup device" success:^(NSString *deviceId) { - MXStrongifyAndReturnIfNil(self); - MXStrongifyAndReturnIfNil(restClient); - MXLogDebug(@"[MXDehydrationService] dehydrateDevice: Preparing device keys for device %@ (current device ID %@)", deviceId, restClient.credentials.deviceId); - MXDeviceInfo *deviceInfo = [[MXDeviceInfo alloc] initWithDeviceId:deviceId]; - deviceInfo.userId = restClient.credentials.userId; - deviceInfo.keys = @{ - [NSString stringWithFormat:@"%@:%@", kMXKeyEd25519Type, deviceId]: e2eKeys[kMXKeyEd25519Type], - [NSString stringWithFormat:@"%@:%@", kMXKeyCurve25519Type, deviceId]: e2eKeys[kMXKeyCurve25519Type] - }; - deviceInfo.algorithms = [[MXCryptoAlgorithms sharedAlgorithms] supportedAlgorithms]; - - // Cross sign and device sign together so that the new session gets automatically validated on upload - MXWeakify(self); - [crossSigning signObject:deviceInfo.signalableJSONDictionary withKeyType:MXCrossSigningKeyType.selfSigning success:^(NSDictionary *signedObject) { - MXStrongifyAndReturnIfNil(self); - - NSMutableDictionary *signatures = [NSMutableDictionary dictionary]; - [signatures addEntriesFromDictionary:signedObject[@"signatures"][restClient.credentials.userId]]; - - NSString *deviceSignature = [account signMessage:[MXCryptoTools canonicalJSONDataForJSON:deviceInfo.signalableJSONDictionary]]; - signatures[[NSString stringWithFormat:@"%@:%@", kMXKeyEd25519Type, deviceInfo.deviceId]] = deviceSignature; - - deviceInfo.signatures = @{restClient.credentials.userId : signatures}; - - [self uploadDeviceInfo:deviceInfo forAccount:account withMatrixRestClient:restClient success:success failure:failure]; - } failure:^(NSError *error) { - MXLogWarning(@"[MXDehydrationService] dehydrateDevice: Failed cross-signing dehydrated device data: %@", error); - failure(error); - }]; - } failure:^(NSError *error) { - [self stopProgress]; - MXLogErrorDetails(@"[MXDehydrationService] dehydrateDevice: Failed pushing dehydrated device data", @{ - @"error": error ?: @"unknown" - }); - failure(error); - }]; -} - -- (void)rehydrateDeviceWithMatrixRestClient:(MXRestClient*)restClient - dehydrationKey:(NSData*)dehydrationKey - success:(void (^)(NSString * deviceId))success - failure:(void (^)(NSError *error))failure; -{ - MXLogDebug(@"[MXDehydrationService] rehydrateDevice: Getting dehydrated device."); - [restClient getDehydratedDeviceWithSuccess:^(MXDehydratedDevice *device) { - if (!device || !device.deviceId) - { - MXLogDebug(@"[MXDehydrationService] rehydrateDevice: No dehydrated device found."); - NSError *error = [NSError errorWithDomain:MXDehydrationServiceErrorDomain - code:MXDehydrationServiceNothingToRehydrateErrorCode - userInfo:@{ - NSLocalizedDescriptionKey: @"No dehydrated device found.", - }]; - failure(error); - return; - } - - if (![device.algorithm isEqual:MXDehydrationAlgorithm]) - { - MXLogError(@"[MXDehydrationService] rehydrateDevice: Invalid dehydrated device algorithm."); - failure([NSError errorWithDomain:MXDehydrationServiceErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Wrong algorithm for dehydrated device"}]); - return; - } - - [restClient claimDehydratedDeviceWithId:device.deviceId Success:^(BOOL isClaimed) { - if (!isClaimed) - { - MXLogDebug(@"[MXDehydrationService] rehydrateDevice: Device already claimed."); - NSError *error = [NSError errorWithDomain:MXDehydrationServiceErrorDomain - code:MXDehydrationServiceAlreadyClaimedErrorCode - userInfo:@{ - NSLocalizedDescriptionKey: @"device already claimed.", - }]; - failure(error); - return; - } - - MXLogDebug(@"[MXDehydrationService] rehydrateDevice: Exporting dehydrated device %@", device.deviceId); - MXCredentials *tmpCredentials = [restClient.credentials copy]; - tmpCredentials.deviceId = device.deviceId; - [MXLegacyCrypto rehydrateExportedOlmDevice:[[MXExportedOlmDevice alloc] initWithAccount:device.account pickleKey:dehydrationKey forSessions:@[]] withCredentials:tmpCredentials complete:^(BOOL stored) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (stored) - { - MXLogDebug(@"[MXDehydrationService] rehydrateDevice: Successfully rehydrated device %@", device.deviceId); - success(device.deviceId); - } - else - { - MXLogError(@"[MXDehydrationService] rehydrateDevice: Failed storing the exported Olm device"); - failure([NSError errorWithDomain:MXDehydrationServiceErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Failed to store the exported Olm device"}]); - } - }); - }]; - } failure:^(NSError *error) { - MXLogErrorDetails(@"[MXDehydrationService] rehydrateDevice: Claiming dehydrated device failed", @{ - @"error": error ?: @"unknown" - }); - failure(error); - }]; - } failure:^(NSError *error) { - MXError *mxError = [[MXError alloc] initWithNSError:error]; - if (mxError && [mxError.errcode isEqualToString:kMXErrCodeStringNotFound]) - { - MXLogDebug(@"[MXDehydrationService] rehydrateDevice: No dehydrated device found."); - NSError *error = [NSError errorWithDomain:MXDehydrationServiceErrorDomain - code:MXDehydrationServiceNothingToRehydrateErrorCode - userInfo:@{ - NSLocalizedDescriptionKey: @"No dehydrated device found.", - }]; - failure(error); - } - else - { - MXLogErrorDetails(@"[MXDehydrationService] rehydrateDevice: DehydratedDeviceId failed", @{ - @"error": error ?: @"unknown" - }); - failure(error); - } - }]; -} - -#pragma mark - Private methods - -- (void)uploadDeviceInfo:(MXDeviceInfo*)deviceInfo - forAccount:(OLMAccount*)account - withMatrixRestClient:(MXRestClient*)restClient - success:(void (^)(NSString *deviceId))success - failure:(void (^)(NSError *error))failure -{ - MXLogDebug(@"[MXDehydrationService] uploadDeviceInfo: preparing one time keys"); - - NSDictionary *oneTimeKeys = [self signKeys:account.oneTimeKeys - withAccount:account - userId:restClient.credentials.userId - deviceId:deviceInfo.deviceId]; - - MXLogDebug(@"[MXDehydrationService] uploadDeviceInfo: preparing fallback keys"); - - NSDictionary *fallbackKeys = [self signKeys:account.fallbackKey - withAccount:account - userId:restClient.credentials.userId - deviceId:deviceInfo.deviceId]; - - MXWeakify(self); - [restClient uploadKeys:deviceInfo.JSONDictionary oneTimeKeys:oneTimeKeys fallbackKeys:fallbackKeys forDeviceWithId:deviceInfo.deviceId success:^(MXKeysUploadResponse *keysUploadResponse) { - [account markOneTimeKeysAsPublished]; - MXLogDebug(@"[MXDehydrationService] uploadDeviceInfo: dehydration done successfully with device ID: %@ ed25519: %@ curve25519: %@", deviceInfo.deviceId, account.identityKeys[kMXKeyEd25519Type], account.identityKeys[kMXKeyCurve25519Type]); - MXStrongifyAndReturnIfNil(self); - [self stopProgress]; - success(deviceInfo.deviceId); - } failure:^(NSError *error) { - MXLogErrorDetails(@"[MXDehydrationService] uploadDeviceInfo: failed uploading device keys", @{ - @"error": error ?: @"unknown" - }); - MXStrongifyAndReturnIfNil(self); - [self stopProgress]; - failure(error); - }]; -} - -#pragma mark - Private methods - -- (void)stopProgress -{ - self.inProgress = NO; -} - -- (NSDictionary *)signKeys:(NSDictionary *)keys - withAccount:(OLMAccount *)account - userId:(NSString *)userId - deviceId:(NSString *)deviceId - -{ - NSMutableDictionary *signedKeys = [NSMutableDictionary dictionary]; - - for (NSString *keyId in keys[kMXKeyCurve25519Type]) - { - NSMutableDictionary *key = [NSMutableDictionary dictionary]; - key[@"key"] = keys[kMXKeyCurve25519Type][keyId]; - - NSString *signature = [account signMessage:[MXCryptoTools canonicalJSONDataForJSON:key]]; - key[@"signatures"] = @{ - userId: @{ - [NSString stringWithFormat:@"%@:%@", kMXKeyEd25519Type, deviceId]: signature - } - }; - - signedKeys[[NSString stringWithFormat:@"%@:%@", kMXKeySignedCurve25519Type, keyId]] = key; - } - - return signedKeys; -} - -@end diff --git a/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.h b/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.h index cb96df857b..700c31c5fb 100644 --- a/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.h +++ b/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.h @@ -31,7 +31,7 @@ extern const struct MXSecretId { __unsafe_unretained NSString *crossSigningSelfSigning; __unsafe_unretained NSString *crossSigningUserSigning; __unsafe_unretained NSString *keyBackup; - + __unsafe_unretained NSString *dehydratedDevice; } MXSecretId; diff --git a/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m b/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m index e17b319c8d..724a1363c0 100644 --- a/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m +++ b/MatrixSDK/Crypto/KeySharing/Secret/MXSecretShareManager.m @@ -30,7 +30,8 @@ .crossSigningMaster = @"m.cross_signing.master", .crossSigningSelfSigning = @"m.cross_signing.self_signing", .crossSigningUserSigning = @"m.cross_signing.user_signing", - .keyBackup = @"m.megolm_backup.v1" + .keyBackup = @"m.megolm_backup.v1", + .dehydratedDevice = @"org.matrix.msc3814" // @"m.dehydrated_device" }; diff --git a/MatrixSDK/Crypto/MXCrypto.h b/MatrixSDK/Crypto/MXCrypto.h index d638850b0d..3f12cdd45f 100644 --- a/MatrixSDK/Crypto/MXCrypto.h +++ b/MatrixSDK/Crypto/MXCrypto.h @@ -40,6 +40,7 @@ @class MXSession; @class MXRoom; +@class DehydrationService; NS_ASSUME_NONNULL_BEGIN @@ -114,6 +115,8 @@ extern NSString *const MXDeviceListDidUpdateUsersDevicesNotification; */ @property (nonatomic, readonly) MXRecoveryService *recoveryService; +@property (nonatomic, readonly) DehydrationService *dehydrationService; + #pragma mark - Crypto start / close /** diff --git a/MatrixSDK/Crypto/MXCryptoV2.swift b/MatrixSDK/Crypto/MXCryptoV2.swift index f700e7fd6d..60fbda52b5 100644 --- a/MatrixSDK/Crypto/MXCryptoV2.swift +++ b/MatrixSDK/Crypto/MXCryptoV2.swift @@ -60,6 +60,7 @@ class MXCryptoV2: NSObject, MXCrypto { let keyVerificationManager: MXKeyVerificationManager let crossSigning: MXCrossSigning let recoveryService: MXRecoveryService + let dehydrationService: DehydrationService @MainActor init( @@ -124,14 +125,13 @@ class MXCryptoV2: NSObject, MXCrypto { ) crossSigning = crossSign + let secretStorage = MXSecretStorage(matrixSession: session, processingQueue: legacyQueue) + recoveryService = MXRecoveryService( dependencies: .init( credentials: restClient.credentials, backup: backup, - secretStorage: MXSecretStorage( - matrixSession: session, - processingQueue: legacyQueue - ), + secretStorage: secretStorage, secretStore: MXCryptoSecretStoreV2( backup: backup, backupEngine: backupEngine, @@ -143,6 +143,10 @@ class MXCryptoV2: NSObject, MXCrypto { delegate: crossSign ) + dehydrationService = DehydrationService(restClient: restClient, + secretStorage: secretStorage, + dehydratedDevices: machine.dehydratedDevices()) + log.debug("Initialized Crypto module") } @@ -330,7 +334,8 @@ class MXCryptoV2: NSObject, MXCrypto { toDevice: syncResponse.toDevice, deviceLists: syncResponse.deviceLists, deviceOneTimeKeysCounts: syncResponse.deviceOneTimeKeysCount ?? [:], - unusedFallbackKeys: syncResponse.unusedFallbackKeys + unusedFallbackKeys: syncResponse.unusedFallbackKeys, + nextBatchToken: syncResponse.nextBatch ) await handle(toDeviceEvents: toDevice.events) } catch { diff --git a/MatrixSDK/Crypto/Recovery/MXRecoveryService.m b/MatrixSDK/Crypto/Recovery/MXRecoveryService.m index ff11f6e77c..ff444a76cc 100644 --- a/MatrixSDK/Crypto/Recovery/MXRecoveryService.m +++ b/MatrixSDK/Crypto/Recovery/MXRecoveryService.m @@ -58,8 +58,10 @@ - (instancetype)initWithDependencies:(MXRecoveryServiceDependencies *)dependenci MXSecretId.crossSigningSelfSigning, MXSecretId.crossSigningUserSigning, MXSecretId.keyBackup, + MXSecretId.dehydratedDevice ]; } + return self; } @@ -690,7 +692,6 @@ - (void)recoverServicesAssociatedWithSecrets:(nullable NSArray*)secre }); } - - (void)recoverKeyBackupWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failure { diff --git a/MatrixSDK/JSONModels/MXJSONModels.h b/MatrixSDK/JSONModels/MXJSONModels.h index c6075b3573..2a758f1f38 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.h +++ b/MatrixSDK/JSONModels/MXJSONModels.h @@ -1379,32 +1379,27 @@ FOUNDATION_EXPORT NSString *const kMXPushRuleScopeStringGlobal; @end -#pragma mark - Dehydration +#pragma mark - Device Dehydration -/** - `MXDehydratedDevice` represents the dehydrated device of the current user. - */ -@interface MXDehydratedDevice : MXJSONModel +@interface MXDehydratedDeviceCreationParameters : MXJSONModel - /** - A unique identifier of the device. - */ - @property (nonatomic) NSString *deviceId; +@property (nonatomic) NSString *body; - /** - The encrypted account data of the dehydrated device (libolm's pickle format) - */ - @property (nonatomic) NSString *account; +@end - /** - The algorithm used for encrypting the account data - */ - @property (nonatomic) NSString *algorithm; +@interface MXDehydratedDeviceResponse : MXJSONModel - /** - The passphrase used for encrypting the account data (optional) - */ - @property (nonatomic) NSString *passphrase; +@property (nonatomic, nonnull) NSString *deviceId; + +@property (nonatomic, nonnull) NSDictionary *deviceData; + +@end + +@interface MXDehydratedDeviceEventsResponse : MXJSONModel + +@property (nonatomic) NSArray *events; + +@property (nonatomic, nullable) NSString *nextBatch; @end diff --git a/MatrixSDK/JSONModels/MXJSONModels.m b/MatrixSDK/JSONModels/MXJSONModels.m index 60a3e7b82f..0052544372 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.m +++ b/MatrixSDK/JSONModels/MXJSONModels.m @@ -2165,62 +2165,37 @@ + (id)modelFromJSON:(NSDictionary *)JSONDictionary @end -#pragma mark - Dehydration +#pragma mark - Device Dehydration -@implementation MXDehydratedDevice +@implementation MXDehydratedDeviceCreationParameters : MXJSONModel -+ (id)modelFromJSON:(NSDictionary *)JSONDictionary +- (NSDictionary *)JSONDictionary { - MXDehydratedDevice *device = [[MXDehydratedDevice alloc] init]; - if (device) - { - MXJSONModelSetString(device.deviceId, JSONDictionary[@"device_id"]); - NSDictionary *deviceData = nil; - MXJSONModelSetDictionary(deviceData, JSONDictionary[@"device_data"]); - MXJSONModelSetString(device.account, deviceData[@"account"]); - MXJSONModelSetString(device.algorithm, deviceData[@"algorithm"]); - MXJSONModelSetString(device.passphrase, deviceData[@"passphrase"]); - } - - return device; + return [MXTools deserialiseJSONString:self.body]; } -- (instancetype)initWithCoder:(NSCoder *)aDecoder -{ - self = [super init]; - if (self) - { - _deviceId = [aDecoder decodeObjectForKey:@"device_id"]; - _account = [aDecoder decodeObjectForKey:@"account"]; - _algorithm = [aDecoder decodeObjectForKey:@"algorithm"]; - _passphrase = [aDecoder decodeObjectForKey:@"passphrase"]; - } - return self; -} +@end -- (void)encodeWithCoder:(NSCoder *)aCoder +@implementation MXDehydratedDeviceResponse + ++ (instancetype)modelFromJSON:(NSDictionary *)JSONDictionary { - [aCoder encodeObject:_deviceId forKey:@"device_id"]; - [aCoder encodeObject:_account forKey:@"account"]; - [aCoder encodeObject:_algorithm forKey:@"algorithm"]; - if (_passphrase) - { - [aCoder encodeObject:_passphrase forKey:@"passphrase"]; - } + MXDehydratedDeviceResponse *dehydratedDevice = [[MXDehydratedDeviceResponse alloc] init]; + MXJSONModelSetString(dehydratedDevice.deviceId, JSONDictionary[@"device_id"]); + MXJSONModelSetDictionary(dehydratedDevice.deviceData, JSONDictionary[@"device_data"]); + return dehydratedDevice; } -- (NSDictionary *)JSONDictionary +@end + +@implementation MXDehydratedDeviceEventsResponse + ++ (instancetype)modelFromJSON:(NSDictionary *)JSONDictionary { - NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithDictionary:@{ - @"algorithm": self.algorithm, - @"account": self.account - }]; - - if (self.passphrase) - { - dictionary[@"passphrase"] = self.passphrase; - } - return dictionary.copy; + MXDehydratedDeviceEventsResponse *dehydratedDevice = [[MXDehydratedDeviceEventsResponse alloc] init]; + MXJSONModelSetArray(dehydratedDevice.events, JSONDictionary[@"events"]); + MXJSONModelSetString(dehydratedDevice.nextBatch, JSONDictionary[@"next_batch"]); + return dehydratedDevice; } @end diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index cd4fdf51d8..981c3fc992 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -2516,47 +2516,49 @@ Note: Clients should consider avoiding this endpoint for URLs posted in encrypte success:(void (^)(MXDeviceListResponse *deviceLists))success failure:(void (^)(NSError *error))failure; - -#pragma mark - Crypto: Dehydration +#pragma mark - Device Dehydration /** - Get the dehydrated device of the current account. - - @param success A block object called when the operation succeeds. It provides a `MXDehydratedDevice` instance of the current account. + Creates a new dehydrated device on the current user's account with the given parameters, coming out of the RustCryptoSDK + @param parameters the device data as received from the RustCryptoSDK + @param success A block object called when the operation succeeds. It provides the ID of the newly dehydrated device. @param failure A block object called when the operation fails. - @return a MXHTTPOperation instance. */ -- (MXHTTPOperation*)getDehydratedDeviceWithSuccess:(void (^)(MXDehydratedDevice *device))success - failure:(void (^)(NSError *error))failure; +- (MXHTTPOperation*)createDehydratedDevice:(MXDehydratedDeviceCreationParameters *)parameters + success:(void (^)(NSString * _Nonnull deviceId))success + failure:(void (^)(NSError * _Nonnull error))failure; /** - Set a given device as the dehydrated device of the current account. - - @param device data of the dehydrated device - @param deviceDisplayName display name of the dehydrated device - @param success A block object called when the operation succeeds. It provides the ID of the newly dehydrated device. + Get the dehydrated device of the current account. + @param success A block object called when the operation succeeds. It provides a `MXDehydratedDeviceResponse` instance of the current account. @param failure A block object called when the operation fails. - @return a MXHTTPOperation instance. - */ -- (MXHTTPOperation*)setDehydratedDevice:(MXDehydratedDevice *)device - withDisplayName:(NSString *)deviceDisplayName - success:(void (^)(NSString *deviceId))success - failure:(void (^)(NSError *error))failure; + */ +- (MXHTTPOperation*)retrieveDehydratedDeviceWithSuccess:(void (^)(MXDehydratedDeviceResponse * _Nonnull dehydratedDevice))success + failure:(void (^)(NSError * _Nonnull error))failure; /** - Claim the dehydrated device of the current account. - - @param deviceId ID of the dehydrated to be claimed. - @param success A block object called when the operation succeeds. + Delete the current dehydrated device + @param success A block object called when the operation succeeds @param failure A block object called when the operation fails. + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)deleteDehydratedDeviceWithSuccess:(void (^)(void))success + failure:(void (^)(NSError * _Nonnull error))failure; +/** + Retrieves the to device events stored on the backend for the given dehydrated device. Results are batched so multiple invocations might be necessary + @param deviceId The dehydrated device id in question + @param nextBatch Pagination token for retrieving more events + @param success A block object called when the operation succeeds. It provides the events and a next batch token + @param failure A block object called when the operation fails. @return a MXHTTPOperation instance. - */ -- (MXHTTPOperation*)claimDehydratedDeviceWithId:(NSString*)deviceId - Success:(void (^)(BOOL success))success - failure:(void (^)(NSError *error))failure; + */ +- (MXHTTPOperation*)retrieveDehydratedDeviceEventsForDeviceId:(NSString *)deviceId + nextBatch:(NSString *)nextBatch + success:(void (^)(MXDehydratedDeviceEventsResponse * _Nonnull dehydratedDeviceEventsResponse))success + failure:(void (^)(NSError * _Nonnull error))failure; #pragma mark - Crypto: e2e keys backup diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index 799514b860..fc73875a27 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -4986,78 +4986,93 @@ - (MXHTTPOperation *)keyChangesFrom:(NSString *)fromToken to:(NSString *)toToken }]; } +#pragma mark - Device dehydration -#pragma mark - Crypto: Dehydration +- (MXHTTPOperation*)createDehydratedDevice:(MXDehydratedDeviceCreationParameters *)parameters + success:(void (^)(NSString *deviceId))success + failure:(void (^)(NSError *error))failure +{ + MXWeakify(self); + return [httpClient requestWithMethod:@"PUT" + path:[NSString stringWithFormat:@"%@/%@/org.matrix.msc3814.v1/dehydrated_device", credentials.homeServer, kMXAPIPrefixPathUnstable] + parameters:parameters.JSONDictionary + success:^(NSDictionary *JSONResponse) { + __block NSString *deviceId; + [self dispatchProcessing:^{ + MXJSONModelSetString(deviceId, JSONResponse[@"device_id"]) + } andCompletion:^{ + success(deviceId); + }]; + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} -- (MXHTTPOperation*)getDehydratedDeviceWithSuccess:(void (^)(MXDehydratedDevice *device))success - failure:(void (^)(NSError *error))failure +- (MXHTTPOperation*)retrieveDehydratedDeviceWithSuccess:(void (^)(MXDehydratedDeviceResponse *dehydratedDevice))success + failure:(void (^)(NSError *error))failure { MXWeakify(self); return [httpClient requestWithMethod:@"GET" - path:[NSString stringWithFormat:@"%@/%@/org.matrix.msc2697.v2/dehydrated_device", credentials.homeServer, kMXAPIPrefixPathUnstable] + path:[NSString stringWithFormat:@"%@/%@/org.matrix.msc3814.v1/dehydrated_device", credentials.homeServer, kMXAPIPrefixPathUnstable] parameters:@{} success:^(NSDictionary *JSONResponse) { - __block MXDehydratedDevice *device; - [self dispatchProcessing:^{ - MXJSONModelSetMXJSONModel(device, MXDehydratedDevice, JSONResponse); - } andCompletion:^{ - success(device); - }]; - } - failure:^(NSError *error) { - MXStrongifyAndReturnIfNil(self); - [self dispatchFailure:error inBlock:failure]; - }]; + __block MXDehydratedDeviceResponse *dehydratedDevice; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(dehydratedDevice, MXDehydratedDeviceResponse, JSONResponse); + } andCompletion:^{ + success(dehydratedDevice); + }]; + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; } -- (MXHTTPOperation*)setDehydratedDevice:(MXDehydratedDevice *)device - withDisplayName:(NSString *)deviceDisplayName - success:(void (^)(NSString *deviceId))success - failure:(void (^)(NSError *error))failure +- (MXHTTPOperation*)deleteDehydratedDeviceWithSuccess:(void (^)(void))success + failure:(void (^)(NSError *error))failure { MXWeakify(self); - return [httpClient requestWithMethod:@"PUT" - path:[NSString stringWithFormat:@"%@/%@/org.matrix.msc2697.v2/dehydrated_device", credentials.homeServer, kMXAPIPrefixPathUnstable] - parameters:@{ - @"initial_device_display_name": deviceDisplayName, - @"device_data": device.JSONDictionary} + return [httpClient requestWithMethod:@"DELETE" + path:[NSString stringWithFormat:@"%@/%@/org.matrix.msc3814.v1/dehydrated_device", credentials.homeServer, kMXAPIPrefixPathUnstable] + parameters:nil success:^(NSDictionary *JSONResponse) { - __block NSString *deviceId; - [self dispatchProcessing:^{ - deviceId = JSONResponse[@"device_id"]; - } andCompletion:^{ - success(deviceId); - }]; - } - failure:^(NSError *error) { - MXStrongifyAndReturnIfNil(self); - [self dispatchFailure:error inBlock:failure]; - }]; + success(); + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; } -- (MXHTTPOperation*)claimDehydratedDeviceWithId:(NSString*)deviceId - Success:(void (^)(BOOL success))success - failure:(void (^)(NSError *error))failure +- (MXHTTPOperation*)retrieveDehydratedDeviceEventsForDeviceId:(NSString *)deviceId + nextBatch:(NSString *)nextBatch + success:(void (^)(MXDehydratedDeviceEventsResponse *dehydratedDeviceEventsResponse))success + failure:(void (^)(NSError *error))failure { + NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; + if (nextBatch != nil) { + parameters[@"next_batch"] = nextBatch; + } + MXWeakify(self); return [httpClient requestWithMethod:@"POST" - path:[NSString stringWithFormat:@"%@/%@/org.matrix.msc2697.v2/dehydrated_device/claim", credentials.homeServer, kMXAPIPrefixPathUnstable] - parameters:@{@"device_id": deviceId} + path:[NSString stringWithFormat:@"%@/%@/org.matrix.msc3814.v1/dehydrated_device/%@/events", credentials.homeServer, kMXAPIPrefixPathUnstable, [MXTools encodeURIComponent:deviceId]] + parameters:parameters success:^(NSDictionary *JSONResponse) { - __block BOOL successValue; - [self dispatchProcessing:^{ - successValue = [JSONResponse[@"success"] boolValue]; - } andCompletion:^{ - success(successValue); - }]; - } - failure:^(NSError *error) { - MXStrongifyAndReturnIfNil(self); - [self dispatchFailure:error inBlock:failure]; - }]; + __block MXDehydratedDeviceEventsResponse *dehydratedDeviceEventsResponse; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(dehydratedDeviceEventsResponse, MXDehydratedDeviceEventsResponse, JSONResponse); + } andCompletion:^{ + success(dehydratedDeviceEventsResponse); + }]; + + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; } - #pragma mark - Crypto: e2e keys backup - (MXHTTPOperation*)createKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion success:(void (^)(NSString *version))success diff --git a/MatrixSDK/MatrixSDK.h b/MatrixSDK/MatrixSDK.h index 799b21c13b..9f56bca97a 100644 --- a/MatrixSDK/MatrixSDK.h +++ b/MatrixSDK/MatrixSDK.h @@ -196,7 +196,6 @@ FOUNDATION_EXPORT NSString *MatrixSDKVersion; #import "MXGroupsSyncResponse.h" #import "MXInvitedGroupSync.h" #import "MXGroupSyncProfile.h" -#import "MXDehydrationService.h" #import "MXBeaconInfo.h" #import "MXBeacon.h" #import "MXEventAssetType.h" diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift index 91dd3f51cc..9456290585 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoMachineUnitTests.swift @@ -156,7 +156,8 @@ class MXCryptoMachineUnitTests: XCTestCase { toDevice: nil, deviceLists: nil, deviceOneTimeKeysCounts: [:], - unusedFallbackKeys: nil + unusedFallbackKeys: nil, + nextBatchToken: "" ) XCTAssertEqual(result.events.count, 0) } @@ -174,7 +175,8 @@ class MXCryptoMachineUnitTests: XCTestCase { toDevice: toDevice, deviceLists: deviceList, deviceOneTimeKeysCounts: [:], - unusedFallbackKeys: nil + unusedFallbackKeys: nil, + nextBatchToken: "" ) XCTAssertEqual(result.events.count, 1) } diff --git a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift index f6788ad758..8ab364c5b9 100644 --- a/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift +++ b/MatrixSDKTests/Crypto/CryptoMachine/MXCryptoProtocolStubs.swift @@ -41,6 +41,10 @@ class DevicesSourceStub: CryptoIdentityStub, MXCryptoDevicesSource { func devices(userId: String) -> [Device] { return devices[userId]?.map { $0.value } ?? [] } + + func dehydratedDevices() -> DehydratedDevicesProtocol { + fatalError() + } } class UserIdentitySourceStub: CryptoIdentityStub, MXCryptoUserIdentitySource { @@ -155,6 +159,10 @@ class CryptoCrossSigningStub: CryptoIdentityStub, MXCryptoCrossSigning { func devices(userId: String) -> [Device] { return devices[userId]?.map { $0.value } ?? [] } + + func dehydratedDevices() -> DehydratedDevicesProtocol { + fatalError() + } } class CryptoVerificationStub: CryptoIdentityStub { diff --git a/MatrixSDKTests/MXDehydrationTests.m b/MatrixSDKTests/MXDehydrationTests.m deleted file mode 100644 index effa122d2b..0000000000 --- a/MatrixSDKTests/MXDehydrationTests.m +++ /dev/null @@ -1,527 +0,0 @@ -// -// Copyright 2021 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 "MatrixSDKTestsData.h" -#import "MatrixSDKTestsE2EData.h" - -#import "MXSession.h" -#import "MXCrypto_Private.h" -#import "MXCrossSigning_Private.h" - -#import "MXSDKOptions.h" - -#import "MXKeyProvider.h" -#import "MXAesKeyData.h" -#import "MXRawDataKey.h" - -#import -#import "MXDehydrationService.h" -#import "MatrixSDKTestsSwiftHeader.h" - -// Do not bother with retain cycles warnings in tests -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-retain-cycles" - -@interface MXDehydrationTests : XCTestCase - -@property (nonatomic, strong) MatrixSDKTestsData *matrixSDKTestsData; -@property (nonatomic, strong) MatrixSDKTestsE2EData *matrixSDKTestsE2EData; - -@property (nonatomic, strong) NSData *dehydrationKey; - -@property (nonatomic, strong) MXDehydrationService *dehydrationService; - -@end - -@implementation MXDehydrationTests - -- (void)setUp -{ - [super setUp]; - - _matrixSDKTestsData = [[MatrixSDKTestsData alloc] init]; - _matrixSDKTestsE2EData = [[MatrixSDKTestsE2EData alloc] initWithMatrixSDKTestsData:_matrixSDKTestsData]; - - _dehydrationKey = [@"6fXK17pQFUrFqOnxt3wrqz8RHkQUT9vQ" dataUsingEncoding:NSUTF8StringEncoding]; - _dehydrationService = [MXDehydrationService new]; -} - -- (void)tearDown -{ - _matrixSDKTestsData = nil; - _matrixSDKTestsE2EData = nil; - _dehydrationService = nil; - - [super tearDown]; -} - -// Check device dehydration -// - Have e2e Alice -// - Alice creates a dehydrated device -// - Alice downloads their own devices keys -// -> Alice must see their dehydrated device --(void)testDehydrateDevice -{ - // - Have e2e Alice - MXWeakify(self); - [self.matrixSDKTestsE2EData doE2ETestWithAliceInARoom:self readyToTest:^(MXSession *mxSession, NSString *roomId, XCTestExpectation *expectation) { - MXStrongifyAndReturnIfNil(self); - [mxSession.crypto.crossSigning setupWithPassword:MXTESTS_ALICE_PWD success:^{ - - // - Alice creates a dehydrated device - [self.dehydrationService dehydrateDeviceWithMatrixRestClient:mxSession.matrixRestClient crossSigning:mxSession.legacyCrypto.legacyCrossSigning dehydrationKey:self.dehydrationKey success:^(NSString *dehydratedDeviceId) { - // - Alice downloads their own devices keys - [mxSession.crypto downloadKeys:@[mxSession.myUserId] forceDownload:YES success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { - - // -> Alice must see her dehydrated device - XCTAssertEqual([usersDevicesInfoMap deviceIdsForUser:mxSession.myUserId].count, 2); - - MXDeviceInfo *dehydratedDevice = [usersDevicesInfoMap objectForDevice:dehydratedDeviceId forUser:mxSession.myUserId]; - XCTAssertNotNil(dehydratedDevice); - XCTAssertEqualObjects(dehydratedDevice.deviceId, dehydratedDeviceId); - XCTAssertTrue([mxSession.legacyCrypto.legacyCrossSigning isDeviceVerified:dehydratedDevice]); - - [expectation fulfill]; - } failure:^(NSError * error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError * error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError * error) { - XCTFail(@"Failed setting up cross-signing with error: %@", error); - [expectation fulfill]; - }]; - }]; -} - -// Check that others can see a dehydrated device -// - Alice and Bob are in an e2e room -// - Bob creates a dehydrated device and logs out -// - Alice downloads Bob's devices keys -// -> Alice must see Bob's dehydrated device --(void)testDehydrateDeviceSeenByOther -{ - // - Alice and Bob are in an e2e room - [self.matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoom:self cryptedBob:YES warnOnUnknowDevices:NO readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { - [bobSession.crypto.crossSigning setupWithPassword:MXTESTS_BOB_PWD success:^{ - - NSString *bobUserId = bobSession.myUserId; - - // - Bob creates a dehydrated device and logs out - [self.dehydrationService dehydrateDeviceWithMatrixRestClient:bobSession.matrixRestClient crossSigning:bobSession.legacyCrypto.legacyCrossSigning dehydrationKey:self.dehydrationKey success:^(NSString *bobDehydratedDeviceId) { - dispatch_async(dispatch_get_main_queue(), ^{ - [bobSession logout:^{ - - // - Alice download Bob's devices keys - [aliceSession.crypto downloadKeys:@[bobUserId] forceDownload:YES success:^(MXUsersDevicesMap *usersDevicesInfoMap, NSDictionary *crossSigningKeysMap) { - - NSLog(@"[MXCryptoTest] User devices: %@", [usersDevicesInfoMap deviceIdsForUser:bobUserId]); - - // -> Alice must see Bob's dehydrated device - XCTAssertEqual([usersDevicesInfoMap deviceIdsForUser:bobUserId].count, 1); - - MXDeviceInfo *bobDehydratedDevice = [usersDevicesInfoMap objectForDevice:bobDehydratedDeviceId forUser:bobUserId]; - XCTAssertNotNil(bobDehydratedDevice); - XCTAssertEqualObjects(bobDehydratedDevice.deviceId, bobDehydratedDeviceId); - - [expectation fulfill]; - - } failure:^(NSError * error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError * error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - }); - } failure:^(NSError * error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError * error) { - XCTFail(@"Failed setting up cross-signing with error: %@", error); - }]; - }]; -} - -// Check that device rehydration fails silently if no dehydrated device exists -// - Bob logs in (no device dehydration) -// - Bob tries to rehydrate a device -// -> Bob should start his session normally --(void)testDeviceRehydrationWithoutDehydratedDevice -{ - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; - - // - Bob logs in (no device dehydration) - [self.matrixSDKTestsData doMXRestClientTestWithBob:self readyToTest:^(MXRestClient *bobRestClient, XCTestExpectation *expectation) { - MXSession *mxSession = [[MXSession alloc] initWithMatrixRestClient:bobRestClient]; - [self.matrixSDKTestsData retain:mxSession]; - - // - Bob tries to rehydrate a device - [self.dehydrationService rehydrateDeviceWithMatrixRestClient:mxSession.matrixRestClient dehydrationKey:self.dehydrationKey success:^(NSString *deviceId) { - XCTFail(@"No rehydrated device should be found."); - [expectation fulfill]; - - } failure:^(NSError *error) { - if ([error.domain isEqual:MXDehydrationServiceErrorDomain] && error.code == MXDehydrationServiceNothingToRehydrateErrorCode) - { - [mxSession start:^{ - // -> Bob should start his session normally - [expectation fulfill]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } - else - { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - } - }]; - }]; -} - -// Check if a dehydrated device can be properly rehydrated -// - Alice is in an e2e room -// - Alice setup a dehydrated device -// - Alice logs off and logs in back -// - Alice rehydrate her device -// -> The rehydrated device must have the same properties --(void)testDehydrateDeviceAndClaimDehydratedDevice -{ - // - Alice is in an e2e room - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; - [self.matrixSDKTestsE2EData doE2ETestWithAliceInARoom:self readyToTest:^(MXSession *aliceSession, NSString *roomId, XCTestExpectation *expectation) { - [aliceSession.crypto.crossSigning setupWithPassword:MXTESTS_ALICE_PWD success:^{ - - NSString *aliceSessionDevice = aliceSession.myDeviceId; - // - Alice setup a dehydrated device - [self.dehydrationService dehydrateDeviceWithMatrixRestClient:aliceSession.matrixRestClient crossSigning:aliceSession.legacyCrypto.legacyCrossSigning dehydrationKey:self.dehydrationKey success:^(NSString *dehydratedDeviceId) { - dispatch_async(dispatch_get_main_queue(), ^{ - // - Alice logs off and logs in back - [self.matrixSDKTestsData loginUserOnANewDevice:self credentials:nil withPassword:MXTESTS_ALICE_PWD sessionToLogout:aliceSession newSessionStore:nil startNewSession:NO e2e:YES onComplete:^(MXSession *aliceSession2) { - - NSString *aliceSession2Device = aliceSession2.myDeviceId; - // - Alice rehydrate her device - [self.dehydrationService rehydrateDeviceWithMatrixRestClient:aliceSession2.matrixRestClient dehydrationKey:self.dehydrationKey success:^(NSString *deviceId) { - // -> The rehydrated device must have the same properties - if (!deviceId) - { - XCTFail(@"device rehydration shouldn't be canceled"); - [expectation fulfill]; - return; - } - aliceSession2.credentials.deviceId = deviceId; - - XCTAssertNotEqualObjects(aliceSessionDevice, aliceSession2Device); - XCTAssertNotEqualObjects(aliceSession2Device, dehydratedDeviceId); - XCTAssertNotEqualObjects(aliceSession2.myDeviceId, aliceSession2Device); - XCTAssertEqualObjects(aliceSession2.myDeviceId, dehydratedDeviceId); - - [aliceSession2 start:^{ - XCTAssertNotNil(aliceSession2.crypto); - XCTAssertEqualObjects(aliceSession2.legacyCrypto.myDevice.deviceId, dehydratedDeviceId); - XCTAssertEqualObjects(aliceSession2.legacyCrypto.store.deviceId, dehydratedDeviceId); - XCTAssertTrue([aliceSession2.crypto.crossSigning canTrustCrossSigning]); - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - - [expectation fulfill]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - }]; - }); - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError *error) { - XCTFail(@"Failed setting up cross-signing with error: %@", error); - }]; - }]; -} - -// Check that a user can receive live message with a rehydrated session -// - Alice and Bob are in an e2e room -// - Alice creates a dehydrated device -// - Alice logs out and logs on -// - Alice rehydrates the new session with the dehydrated device -// - And starts her new session with e2e enabled -// - Bob sends a message -// -> Alice must be able to receive and decrypt the message sent by Bob --(void)testReceiveLiveMessageAfterDeviceRehydration -{ - // - Alice and Bob are in an e2e room - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; - [self.matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { - [aliceSession.crypto.crossSigning setupWithPassword:MXTESTS_ALICE_PWD success:^{ - - // - Alice creates a dehydrated device - [self.dehydrationService dehydrateDeviceWithMatrixRestClient:aliceSession.matrixRestClient crossSigning:aliceSession.legacyCrypto.legacyCrossSigning dehydrationKey:self.dehydrationKey success:^(NSString *deviceId) { - dispatch_async(dispatch_get_main_queue(), ^{ - // - Alice logs out and logs on - [self.matrixSDKTestsData loginUserOnANewDevice:self credentials:nil withPassword:MXTESTS_ALICE_PWD sessionToLogout:aliceSession newSessionStore:nil startNewSession:NO e2e:YES onComplete:^(MXSession *aliceSession2) { - - MXRestClient *aliceRestClient = aliceSession2.matrixRestClient; - - MXSession *aliceSession3 = [[MXSession alloc] initWithMatrixRestClient:aliceRestClient]; - [self.matrixSDKTestsData retain:aliceSession3]; - - [aliceSession2 close]; - - // - Alice rehydrates the new session with the dehydrated device - [self.dehydrationService rehydrateDeviceWithMatrixRestClient:aliceSession3.matrixRestClient dehydrationKey:self.dehydrationKey success:^(NSString *rehydratedDeviceId) { - if (!rehydratedDeviceId) - { - XCTFail(@"device rehydration shouldn't be canceled"); - [expectation fulfill]; - return; - } - aliceSession3.credentials.deviceId = rehydratedDeviceId; - - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; - - // - And starts her new session with e2e enabled - [aliceSession3 setStore:[MXMemoryStore new] success:^{ - - [aliceSession3 start:^{ - MXRoom *roomFromBobPOV = [bobSession roomWithRoomId:roomId]; - MXRoom *roomFromAlice3POV = [aliceSession3 roomWithRoomId:roomId]; - - XCTAssertNotNil(roomFromBobPOV, @"roomFromBobPOV shouldn't be nil"); - - if (!roomFromAlice3POV) - { - XCTFail(@"Not able to get room with Alice's session"); - [expectation fulfill]; - return; - } - - NSString *messageFromBob = @"Hello I'm Bob!"; - - [roomFromAlice3POV liveTimeline:^(id liveTimeline) { - // -> Alice must be able to receive and decrypt the message sent by Bob - [liveTimeline listenToEventsOfTypes:@[kMXEventTypeStringRoomMessage, kMXEventTypeStringRoomEncrypted] onEvent:^(MXEvent *event, MXTimelineDirection direction, MXRoomState *roomState) { - - XCTAssertEqual(0, [self checkEncryptedEvent:event roomId:roomId clearMessage:messageFromBob senderSession:bobSession]); - - [expectation fulfill]; - - }]; - }]; - - // - Bob sends a message - [roomFromBobPOV sendTextMessage:messageFromBob threadId:nil success:nil failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - }]; - }); - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError *error) { - XCTFail(@"Failed setting up cross-signing with error: %@", error); - }]; - }]; - return; -} - -// Check that others can see a dehydrated device -// - Alice and Bob are in an e2e room -// - Bob creates a dehydrated device and logs out -// - Alice sends a message -// - Bob logs in on a new device -// - Bob rehydrates the new session with the dehydrated device -// - And starts their new session with e2e enabled -// -> Bob must be able to decrypt the message sent by Alice --(void)testReceiveMessageWhileBeingSignedOffWithDeviceRehydration -{ - // - Alice and Bob are in an e2e room - [self.matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoom:self cryptedBob:YES warnOnUnknowDevices:NO readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { - [bobSession.crypto.crossSigning setupWithPassword:MXTESTS_BOB_PWD success:^{ - MXCredentials *bobCredentials = bobSession.credentials; - - // - Bob creates a dehydrated device and logs out - [self.dehydrationService dehydrateDeviceWithMatrixRestClient:bobSession.matrixRestClient crossSigning:bobSession.legacyCrypto.legacyCrossSigning dehydrationKey:self.dehydrationKey success:^(NSString *bobDehydratedDeviceId) { - dispatch_async(dispatch_get_main_queue(), ^{ - [bobSession logout:^{ - [bobSession close]; - - // - Alice sends a message - NSString *message = @"Hello I'm Alice!"; - MXRoom *roomFromAlicePOV = [aliceSession roomWithRoomId:roomId]; - [roomFromAlicePOV sendTextMessage:message threadId:nil success:^(NSString *eventId) { - - // - Bob logs in on a new device - [self.matrixSDKTestsData loginUserOnANewDevice:self credentials:bobCredentials withPassword:MXTESTS_BOB_PWD sessionToLogout:nil newSessionStore:nil startNewSession:NO e2e:YES onComplete:^(MXSession *bobSession2) { - - // - Bob rehydrates the new session with the dehydrated device - [self.dehydrationService rehydrateDeviceWithMatrixRestClient:bobSession2.matrixRestClient dehydrationKey:self.dehydrationKey success:^(NSString *deviceId) { - if (!deviceId) - { - XCTFail(@"device rehydration shouldn't be canceled"); - [expectation fulfill]; - return; - } - bobSession2.credentials.deviceId = deviceId; - - // - And starts their new session with e2e enabled - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; - [bobSession2 start:^{ - [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; - - // -> Bob must be able to decrypt the message sent by Alice - [bobSession2 eventWithEventId:eventId inRoom:roomId success:^(MXEvent *event) { - - XCTAssertEqual(event.wireEventType, MXEventTypeRoomEncrypted); - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - XCTAssertEqualObjects(event.content[kMXMessageBodyKey], message); - XCTAssertNil(event.decryptionError); - - [expectation fulfill]; - - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - - } failure:^(NSError *error) { - XCTFail(@"The request should not fail - NSError: %@", error); - [expectation fulfill]; - }]; - }]; - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - }); - } failure:^(NSError *error) { - XCTFail(@"Cannot set up intial test conditions - error: %@", error); - [expectation fulfill]; - }]; - } failure:^(NSError *error) { - XCTFail(@"Failed setting up cross-signing with error: %@", error); - }]; - }]; -} - -// Test for pickling / unpinling OLM acount -// - create a new OLM Acount -// - pickle the OLM account -// - unpickle the pickled account -// -> identity keys must be the same --(void)testDataPickling -{ - // - create a new OLM Acount - OLMAccount *account = [[OLMAccount alloc] initNewAccount]; - NSDictionary *e2eKeys = [account identityKeys]; - - [account generateOneTimeKeys:50]; - NSDictionary *oneTimeKeys = [account oneTimeKeys]; - - [account generateFallbackKey]; - NSDictionary *fallbackKey = [account fallbackKey]; - - // - pickle the OLM account - NSData *key = [@"6fXK17pQFUrFqOnxt3wrqz8RHkQUT9vQ" dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error = nil; - NSString *serializedAccount = [account serializeDataWithKey:key error:&error]; - - XCTAssertNil(error, "serializeDataWithKey failed due to error %@", error); - - // - unpickle the pickled account - OLMAccount *deserializedAccount = [[OLMAccount alloc] initWithSerializedData:serializedAccount key:key error:&error]; - NSDictionary *deserializedE2eKeys = [deserializedAccount identityKeys]; - NSDictionary *deserializedOneTimeKeys = [deserializedAccount oneTimeKeys]; - NSDictionary *deserializedFallbackKey = [deserializedAccount fallbackKey]; - - // -> identity keys must be the same - XCTAssertNil(error, "initWithSerializedData failed due to error %@", error); - XCTAssert([e2eKeys[@"ed25519"] isEqual:deserializedE2eKeys[@"ed25519"]], @"wrong deserialized ed25519 key %@ != %@", e2eKeys[@"ed25519"], deserializedE2eKeys[@"ed25519"]); - XCTAssert([e2eKeys[@"curve25519"] isEqual:deserializedE2eKeys[@"curve25519"]], @"wrong deserialized curve25519 key %@ != %@", e2eKeys[@"curve25519"], deserializedE2eKeys[@"curve25519"]); - - XCTAssert([oneTimeKeys isEqualToDictionary:deserializedOneTimeKeys]); - XCTAssert([fallbackKey isEqualToDictionary:deserializedFallbackKey]); -} - -#pragma mark - Private methods - -- (NSUInteger)checkEncryptedEvent:(MXEvent*)event roomId:(NSString*)roomId clearMessage:(NSString*)clearMessage senderSession:(MXSession*)senderSession -{ - NSUInteger failureCount = self.testRun.failureCount; - - // Check raw event (encrypted) data as sent by the hs - XCTAssertEqual(event.wireEventType, MXEventTypeRoomEncrypted); - XCTAssertNil(event.wireContent[kMXMessageBodyKey], @"No body field in an encrypted content"); - XCTAssertEqualObjects(event.wireContent[@"algorithm"], kMXCryptoMegolmAlgorithm); - XCTAssertNotNil(event.wireContent[@"ciphertext"]); - XCTAssertNotNil(event.wireContent[@"session_id"]); - XCTAssertNotNil(event.wireContent[@"sender_key"]); - XCTAssertEqualObjects(event.wireContent[@"device_id"], senderSession.legacyCrypto.store.deviceId); - - // Check decrypted data - XCTAssert(event.eventId); - XCTAssertEqualObjects(event.roomId, roomId); - XCTAssertEqual(event.eventType, MXEventTypeRoomMessage); - XCTAssertLessThan(event.age, 10000); - XCTAssertEqualObjects(event.content[kMXMessageBodyKey], clearMessage); - XCTAssertEqualObjects(event.sender, senderSession.myUser.userId); - XCTAssertNil(event.decryptionError); - - // Return the number of failures in this method - return self.testRun.failureCount - failureCount; -} - -@end - -#pragma clang diagnostic pop - diff --git a/Podfile b/Podfile index 95db378dd3..e6c59ed526 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ abstract_target 'MatrixSDK' do pod 'Realm', '10.27.0' pod 'libbase58', '~> 0.1.4' - pod 'MatrixSDKCrypto', "0.3.11", :inhibit_warnings => true + pod 'MatrixSDKCrypto', "0.3.12", :inhibit_warnings => true target 'MatrixSDK-iOS' do platform :ios, '11.0' diff --git a/Podfile.lock b/Podfile.lock index 78719f7e18..5dbcefd7eb 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -16,7 +16,7 @@ PODS: - AFNetworking/NSURLSession - GZIP (1.3.0) - libbase58 (0.1.4) - - MatrixSDKCrypto (0.3.11) + - MatrixSDKCrypto (0.3.12) - OHHTTPStubs (9.1.0): - OHHTTPStubs/Default (= 9.1.0) - OHHTTPStubs/Core (9.1.0) @@ -44,7 +44,7 @@ DEPENDENCIES: - AFNetworking (~> 4.0.0) - GZIP (~> 1.3.0) - libbase58 (~> 0.1.4) - - MatrixSDKCrypto (= 0.3.11) + - MatrixSDKCrypto (= 0.3.12) - OHHTTPStubs (~> 9.1.0) - OLMKit (~> 3.2.5) - Realm (= 10.27.0) @@ -65,12 +65,12 @@ SPEC CHECKSUMS: AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58 GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3 libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd - MatrixSDKCrypto: 475f3dd0058864839c80a464ce76dcdcc58e5c32 + MatrixSDKCrypto: 25929a40733b4ab54f659aaf6a730552a0a06504 OHHTTPStubs: 90eac6d8f2c18317baeca36698523dc67c513831 OLMKit: da115f16582e47626616874e20f7bb92222c7a51 Realm: 9ca328bd7e700cc19703799785e37f77d1a130f2 SwiftyBeaver: 84069991dd5dca07d7069100985badaca7f0ce82 -PODFILE CHECKSUM: 86a634fc24c104e3b913296f2d5e9eb4bf4202cc +PODFILE CHECKSUM: e70d3694981265116ff81a851fb0c1c9654995cd COCOAPODS: 1.12.1 diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a9fdad60b3..c029c05bd3 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -18,7 +18,7 @@ platform :ios do before_all do # Ensure used Xcode version - xcversion(version: "~> 13.2") + xcversion(version: "14.2") end #### Pod ####