diff --git a/CHANGES.rst b/CHANGES.rst index 84b6c987cf..19800ce8df 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,10 +1,12 @@ -Changes in Matrix iOS SDK in 0.11.x (2018-xx-xx) +Changes in Matrix iOS SDK in 0.12.0 (2018-11-) =============================================== Improvements: + * MXCrypto: Add the MXKeyBackup module to manage e2e keys backup (vector-im/riot-ios#2070). * MXMediaManager/MXMediaLoader: Do not allow non-mxc content URLs. * MXMediaManager: Add a constructor based on a homeserver URL, to handle directly the Matrix Content URI (mxc://...). * MXSession: Add a MediaManager instance to handle the media stored on the Matrix Content repository. + * Tests: Make MXRealmCryptoStore work the first time tests are launched on simulators for iOS 11 and higher. Bug Fix: * MXRestClient: [avatarUrlForUser:success:failure]: the returned url is always nil. @@ -38,6 +40,9 @@ Bug fix: * Left room is still displayed as "Empty room" in rooms list (vector-im/riot-ios/issues/2082). * Reply of reply with unexpected newlines renders badly (vector-im/riot-ios/issues/2086). +API break: +* MXCrypto: importRoomKeys methods now return number of imported keys. + Changes in Matrix iOS SDK in 0.11.5 (2018-10-05) =============================================== diff --git a/MatrixSDK.podspec b/MatrixSDK.podspec index 7c8f226ed5..f6c3e74f4b 100644 --- a/MatrixSDK.podspec +++ b/MatrixSDK.podspec @@ -37,6 +37,7 @@ Pod::Spec.new do |s| # Requirements for e2e encryption ss.dependency 'OLMKit', '~> 3.0.0' ss.dependency 'Realm', '~> 3.11.1' + ss.dependency 'libbase58', '~> 0.1.4' end s.subspec 'JingleCallStack' do |ss| diff --git a/MatrixSDK.xcodeproj/project.pbxproj b/MatrixSDK.xcodeproj/project.pbxproj index 8ce4c3a1b9..1017763795 100644 --- a/MatrixSDK.xcodeproj/project.pbxproj +++ b/MatrixSDK.xcodeproj/project.pbxproj @@ -11,6 +11,14 @@ 021AFBA52179E91900742B2C /* MXEncryptedContentKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 021AFBA12179E91800742B2C /* MXEncryptedContentKey.m */; }; 021AFBA62179E91900742B2C /* MXEncryptedContentKey.h in Headers */ = {isa = PBXBuildFile; fileRef = 021AFBA22179E91800742B2C /* MXEncryptedContentKey.h */; settings = {ATTRIBUTES = (Public, ); }; }; 021AFBA72179E91900742B2C /* MXEncryptedContentFile.m in Sources */ = {isa = PBXBuildFile; fileRef = 021AFBA32179E91800742B2C /* MXEncryptedContentFile.m */; }; + 320A883C217F4E35002EA952 /* MXMegolmBackupCreationInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 320A883A217F4E35002EA952 /* MXMegolmBackupCreationInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 320A883D217F4E35002EA952 /* MXMegolmBackupCreationInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 320A883B217F4E35002EA952 /* MXMegolmBackupCreationInfo.m */; }; + 320A8840217F4E3F002EA952 /* MXMegolmBackupAuthData.h in Headers */ = {isa = PBXBuildFile; fileRef = 320A883E217F4E3E002EA952 /* MXMegolmBackupAuthData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 320A8841217F4E3F002EA952 /* MXMegolmBackupAuthData.m in Sources */ = {isa = PBXBuildFile; fileRef = 320A883F217F4E3F002EA952 /* MXMegolmBackupAuthData.m */; }; + 02CAD438217DD12F0074700B /* MXContentScanResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 02CAD433217DD12F0074700B /* MXContentScanResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 02CAD439217DD12F0074700B /* MXContentScanResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 02CAD434217DD12F0074700B /* MXContentScanResult.m */; }; + 02CAD43A217DD12F0074700B /* MXContentScanEncryptedBody.m in Sources */ = {isa = PBXBuildFile; fileRef = 02CAD436217DD12F0074700B /* MXContentScanEncryptedBody.m */; }; + 02CAD43B217DD12F0074700B /* MXContentScanEncryptedBody.h in Headers */ = {isa = PBXBuildFile; fileRef = 02CAD437217DD12F0074700B /* MXContentScanEncryptedBody.h */; settings = {ATTRIBUTES = (Public, ); }; }; 320BBF3C1D6C7D9D0079890E /* MXEventsEnumerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 320BBF3B1D6C7D9D0079890E /* MXEventsEnumerator.h */; settings = {ATTRIBUTES = (Public, ); }; }; 320BBF411D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 320BBF3D1D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.m */; }; 320BBF421D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.h in Headers */ = {isa = PBXBuildFile; fileRef = 320BBF3E1D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -130,9 +138,12 @@ 3283F7781EAF30F700C1688C /* MXBugReportRestClient.h in Headers */ = {isa = PBXBuildFile; fileRef = 3283F7761EAF30F700C1688C /* MXBugReportRestClient.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3283F7791EAF30F700C1688C /* MXBugReportRestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 3283F7771EAF30F700C1688C /* MXBugReportRestClient.m */; }; 3284A5A01DB7C00600A09972 /* MXCryptoStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 3284A59D1DB7C00600A09972 /* MXCryptoStore.h */; }; + 328BCB3321947BE200A976D3 /* MXKeyBackupVersionTrust.h in Headers */ = {isa = PBXBuildFile; fileRef = 328BCB3121947BE200A976D3 /* MXKeyBackupVersionTrust.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 328BCB3421947BE200A976D3 /* MXKeyBackupVersionTrust.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BCB3221947BE200A976D3 /* MXKeyBackupVersionTrust.m */; }; 328DDEC11A07E57E008C7DC8 /* MXJSONModelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 328DDEC01A07E57E008C7DC8 /* MXJSONModelTests.m */; }; 3291D4D41A68FFEB00C3BA41 /* MXFileRoomStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 3291D4D21A68FFEB00C3BA41 /* MXFileRoomStore.h */; }; 3291D4D51A68FFEB00C3BA41 /* MXFileRoomStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 3291D4D31A68FFEB00C3BA41 /* MXFileRoomStore.m */; }; + 32935F61216FA49D00A1BC24 /* MXCryptoBackupTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32935F60216FA49D00A1BC24 /* MXCryptoBackupTests.m */; }; 3293C700214BBA4F009B3DDB /* MXPeekingRoomSummary.h in Headers */ = {isa = PBXBuildFile; fileRef = 3293C6FE214BBA4F009B3DDB /* MXPeekingRoomSummary.h */; }; 3293C701214BBA4F009B3DDB /* MXPeekingRoomSummary.m in Sources */ = {isa = PBXBuildFile; fileRef = 3293C6FF214BBA4F009B3DDB /* MXPeekingRoomSummary.m */; }; 32954019216385F100E300FC /* MXServerNoticeContent.h in Headers */ = {isa = PBXBuildFile; fileRef = 32954017216385F100E300FC /* MXServerNoticeContent.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -187,6 +198,12 @@ 32BA86AC21529E29008F277E /* MXRoomNameStringsLocalizable.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BA86AB21529AE3008F277E /* MXRoomNameStringsLocalizable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32BA86AF2152A79E008F277E /* MXRoomNameDefaultStringLocalizations.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BA86AD2152A79E008F277E /* MXRoomNameDefaultStringLocalizations.h */; }; 32BA86B02152A79E008F277E /* MXRoomNameDefaultStringLocalizations.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BA86AE2152A79E008F277E /* MXRoomNameDefaultStringLocalizations.m */; }; + 32BBAE6C2178E99100D85F46 /* MXKeyBackupData.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBAE662178E99100D85F46 /* MXKeyBackupData.m */; }; + 32BBAE6D2178E99100D85F46 /* MXKeyBackupVersion.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBAE672178E99100D85F46 /* MXKeyBackupVersion.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32BBAE6F2178E99100D85F46 /* MXKeyBackupVersion.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBAE692178E99100D85F46 /* MXKeyBackupVersion.m */; }; + 32BBAE702178E99100D85F46 /* MXKeyBackupData.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBAE6A2178E99100D85F46 /* MXKeyBackupData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32BBAE742179CF4000D85F46 /* MXKeyBackup.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BBAE722179CF4000D85F46 /* MXKeyBackup.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 32BBAE752179CF4000D85F46 /* MXKeyBackup.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BBAE732179CF4000D85F46 /* MXKeyBackup.m */; }; 32BD34BE1E84134A006EDC0D /* MatrixSDKTestsE2EData.m in Sources */ = {isa = PBXBuildFile; fileRef = 32BD34BD1E84134A006EDC0D /* MatrixSDKTestsE2EData.m */; }; 32BED28F1B00A23F00E668FE /* MXCallStack.h in Headers */ = {isa = PBXBuildFile; fileRef = 32BED28E1B00A23F00E668FE /* MXCallStack.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32C03CB62123076F00D92712 /* DirectRoomTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C03CB52123076F00D92712 /* DirectRoomTests.m */; }; @@ -232,6 +249,8 @@ 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 */; }; + 32FFB4F1217E146A00C96002 /* MXRecoveryKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FFB4EF217E146A00C96002 /* MXRecoveryKey.m */; }; 71DE22E01BC7C51200284153 /* MXReceiptData.m in Sources */ = {isa = PBXBuildFile; fileRef = 71DE22DC1BC7C51200284153 /* MXReceiptData.m */; }; 71DE22E11BC7C51200284153 /* MXReceiptData.h in Headers */ = {isa = PBXBuildFile; fileRef = 71DE22DD1BC7C51200284153 /* MXReceiptData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 92634B7F1EF2A37A00DB9F60 /* MXCallAudioSessionConfigurator.h in Headers */ = {isa = PBXBuildFile; fileRef = 92634B7E1EF2A37A00DB9F60 /* MXCallAudioSessionConfigurator.h */; }; @@ -315,8 +334,16 @@ 021AFBA12179E91800742B2C /* MXEncryptedContentKey.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXEncryptedContentKey.m; sourceTree = ""; }; 021AFBA22179E91800742B2C /* MXEncryptedContentKey.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXEncryptedContentKey.h; sourceTree = ""; }; 021AFBA32179E91800742B2C /* MXEncryptedContentFile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXEncryptedContentFile.m; sourceTree = ""; }; + 02CAD433217DD12F0074700B /* MXContentScanResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXContentScanResult.h; sourceTree = ""; }; + 02CAD434217DD12F0074700B /* MXContentScanResult.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXContentScanResult.m; sourceTree = ""; }; + 02CAD436217DD12F0074700B /* MXContentScanEncryptedBody.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXContentScanEncryptedBody.m; sourceTree = ""; }; + 02CAD437217DD12F0074700B /* MXContentScanEncryptedBody.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXContentScanEncryptedBody.h; sourceTree = ""; }; 0BCFBADF157F3C8C43112BD6 /* Pods-MatrixSDKTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatrixSDKTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MatrixSDKTests/Pods-MatrixSDKTests.release.xcconfig"; sourceTree = ""; }; 2BF02FACC417CA3368671024 /* Pods-MatrixSDK.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MatrixSDK.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MatrixSDK/Pods-MatrixSDK.debug.xcconfig"; sourceTree = ""; }; + 320A883A217F4E35002EA952 /* MXMegolmBackupCreationInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXMegolmBackupCreationInfo.h; sourceTree = ""; }; + 320A883B217F4E35002EA952 /* MXMegolmBackupCreationInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXMegolmBackupCreationInfo.m; sourceTree = ""; }; + 320A883E217F4E3E002EA952 /* MXMegolmBackupAuthData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXMegolmBackupAuthData.h; sourceTree = ""; }; + 320A883F217F4E3F002EA952 /* MXMegolmBackupAuthData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXMegolmBackupAuthData.m; sourceTree = ""; }; 320BBF3B1D6C7D9D0079890E /* MXEventsEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXEventsEnumerator.h; sourceTree = ""; }; 320BBF3D1D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXEventsByTypesEnumeratorOnArray.m; sourceTree = ""; }; 320BBF3E1D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXEventsByTypesEnumeratorOnArray.h; sourceTree = ""; }; @@ -438,9 +465,12 @@ 3283F7761EAF30F700C1688C /* MXBugReportRestClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXBugReportRestClient.h; sourceTree = ""; }; 3283F7771EAF30F700C1688C /* MXBugReportRestClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXBugReportRestClient.m; sourceTree = ""; }; 3284A59D1DB7C00600A09972 /* MXCryptoStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCryptoStore.h; sourceTree = ""; }; + 328BCB3121947BE200A976D3 /* MXKeyBackupVersionTrust.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXKeyBackupVersionTrust.h; sourceTree = ""; }; + 328BCB3221947BE200A976D3 /* MXKeyBackupVersionTrust.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXKeyBackupVersionTrust.m; sourceTree = ""; }; 328DDEC01A07E57E008C7DC8 /* MXJSONModelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXJSONModelTests.m; sourceTree = ""; }; 3291D4D21A68FFEB00C3BA41 /* MXFileRoomStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXFileRoomStore.h; sourceTree = ""; }; 3291D4D31A68FFEB00C3BA41 /* MXFileRoomStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXFileRoomStore.m; sourceTree = ""; }; + 32935F60216FA49D00A1BC24 /* MXCryptoBackupTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXCryptoBackupTests.m; sourceTree = ""; }; 3293C6FE214BBA4F009B3DDB /* MXPeekingRoomSummary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXPeekingRoomSummary.h; sourceTree = ""; }; 3293C6FF214BBA4F009B3DDB /* MXPeekingRoomSummary.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXPeekingRoomSummary.m; sourceTree = ""; }; 32954017216385F100E300FC /* MXServerNoticeContent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXServerNoticeContent.h; sourceTree = ""; }; @@ -497,6 +527,12 @@ 32BA86AB21529AE3008F277E /* MXRoomNameStringsLocalizable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRoomNameStringsLocalizable.h; sourceTree = ""; }; 32BA86AD2152A79E008F277E /* MXRoomNameDefaultStringLocalizations.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRoomNameDefaultStringLocalizations.h; sourceTree = ""; }; 32BA86AE2152A79E008F277E /* MXRoomNameDefaultStringLocalizations.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRoomNameDefaultStringLocalizations.m; sourceTree = ""; }; + 32BBAE662178E99100D85F46 /* MXKeyBackupData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXKeyBackupData.m; sourceTree = ""; }; + 32BBAE672178E99100D85F46 /* MXKeyBackupVersion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXKeyBackupVersion.h; sourceTree = ""; }; + 32BBAE692178E99100D85F46 /* MXKeyBackupVersion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXKeyBackupVersion.m; sourceTree = ""; }; + 32BBAE6A2178E99100D85F46 /* MXKeyBackupData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXKeyBackupData.h; sourceTree = ""; }; + 32BBAE722179CF4000D85F46 /* MXKeyBackup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXKeyBackup.h; sourceTree = ""; }; + 32BBAE732179CF4000D85F46 /* MXKeyBackup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXKeyBackup.m; sourceTree = ""; }; 32BD34BC1E84134A006EDC0D /* MatrixSDKTestsE2EData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MatrixSDKTestsE2EData.h; sourceTree = ""; }; 32BD34BD1E84134A006EDC0D /* MatrixSDKTestsE2EData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MatrixSDKTestsE2EData.m; sourceTree = ""; }; 32BED28E1B00A23F00E668FE /* MXCallStack.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXCallStack.h; sourceTree = ""; }; @@ -548,6 +584,9 @@ 32FCAB4C19E578860049C555 /* MXRestClientTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MXRestClientTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 32FE41341D0AB7070060835E /* MXEnumConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXEnumConstants.h; sourceTree = ""; }; 32FE41351D0AB7070060835E /* MXEnumConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXEnumConstants.m; sourceTree = ""; }; + 32FFB4ED217DC0E900C96002 /* MXKeyBackup_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXKeyBackup_Private.h; sourceTree = ""; }; + 32FFB4EE217E146A00C96002 /* MXRecoveryKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MXRecoveryKey.h; sourceTree = ""; }; + 32FFB4EF217E146A00C96002 /* MXRecoveryKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MXRecoveryKey.m; sourceTree = ""; }; 3ABDD5D65684E6B7F52FB94E /* libPods-MatrixSDKTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MatrixSDKTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 71DE22DC1BC7C51200284153 /* MXReceiptData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MXReceiptData.m; sourceTree = ""; }; 71DE22DD1BC7C51200284153 /* MXReceiptData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MXReceiptData.h; sourceTree = ""; }; @@ -627,6 +666,33 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 02CAD431217DD12F0074700B /* ContentScan */ = { + isa = PBXGroup; + children = ( + 02CAD432217DD12F0074700B /* JSONModels */, + 02CAD435217DD12F0074700B /* Data */, + ); + path = ContentScan; + sourceTree = ""; + }; + 02CAD432217DD12F0074700B /* JSONModels */ = { + isa = PBXGroup; + children = ( + 02CAD433217DD12F0074700B /* MXContentScanResult.h */, + 02CAD434217DD12F0074700B /* MXContentScanResult.m */, + ); + path = JSONModels; + sourceTree = ""; + }; + 02CAD435217DD12F0074700B /* Data */ = { + isa = PBXGroup; + children = ( + 02CAD436217DD12F0074700B /* MXContentScanEncryptedBody.m */, + 02CAD437217DD12F0074700B /* MXContentScanEncryptedBody.h */, + ); + path = Data; + sourceTree = ""; + }; 238F35931D935D88B55C4FBC /* Pods */ = { isa = PBXGroup; children = ( @@ -762,6 +828,7 @@ 324BE4651E3FADB1008D99D4 /* Utils */, 322A51C01D9AC8FE00C8536D /* Algorithms */, 32A1513B1DAF768D00400192 /* Data */, + 32BBAE642178E99100D85F46 /* KeyBackup */, 32FA10B21FA1C28100E54233 /* KeySharing */, 322A51B41D9AB15900C8536D /* MXCrypto.h */, 322A51B51D9AB15900C8536D /* MXCrypto.m */, @@ -972,6 +1039,36 @@ name = Lib; sourceTree = ""; }; + 32BBAE642178E99100D85F46 /* KeyBackup */ = { + isa = PBXGroup; + children = ( + 32BBAE652178E99100D85F46 /* Data */, + 32BBAE722179CF4000D85F46 /* MXKeyBackup.h */, + 32BBAE732179CF4000D85F46 /* MXKeyBackup.m */, + 32FFB4ED217DC0E900C96002 /* MXKeyBackup_Private.h */, + 32FFB4EE217E146A00C96002 /* MXRecoveryKey.h */, + 32FFB4EF217E146A00C96002 /* MXRecoveryKey.m */, + ); + path = KeyBackup; + sourceTree = ""; + }; + 32BBAE652178E99100D85F46 /* Data */ = { + isa = PBXGroup; + children = ( + 32BBAE6A2178E99100D85F46 /* MXKeyBackupData.h */, + 32BBAE662178E99100D85F46 /* MXKeyBackupData.m */, + 32BBAE672178E99100D85F46 /* MXKeyBackupVersion.h */, + 32BBAE692178E99100D85F46 /* MXKeyBackupVersion.m */, + 328BCB3121947BE200A976D3 /* MXKeyBackupVersionTrust.h */, + 328BCB3221947BE200A976D3 /* MXKeyBackupVersionTrust.m */, + 320A883E217F4E3E002EA952 /* MXMegolmBackupAuthData.h */, + 320A883F217F4E3F002EA952 /* MXMegolmBackupAuthData.m */, + 320A883A217F4E35002EA952 /* MXMegolmBackupCreationInfo.h */, + 320A883B217F4E35002EA952 /* MXMegolmBackupCreationInfo.m */, + ); + path = Data; + sourceTree = ""; + }; 32C6F92319DD814400EA4E9C = { isa = PBXGroup; children = ( @@ -999,6 +1096,7 @@ 32C6F92F19DD814400EA4E9C /* MatrixSDK */ = { isa = PBXGroup; children = ( + 02CAD431217DD12F0074700B /* ContentScan */, 322A51B31D9AB13E00C8536D /* Crypto */, 320DFDC719DD99B60068622A /* Data */, 3281E8B219E42DFE00976E1A /* JSONModels */, @@ -1035,6 +1133,7 @@ 32C6F93919DD814400EA4E9C /* MatrixSDKTests */ = { isa = PBXGroup; children = ( + 32935F60216FA49D00A1BC24 /* MXCryptoBackupTests.m */, 32C03CB52123076F00D92712 /* DirectRoomTests.m */, 320E1BC01E0AD674009635F5 /* MXRoomSummaryTests.m */, 32322A471E57264E005DD155 /* MXSelfSignedHomeserverTests.m */, @@ -1258,6 +1357,7 @@ 32954019216385F100E300FC /* MXServerNoticeContent.h in Headers */, 327187851DA7D0220071C818 /* MXOlmDecryption.h in Headers */, 32D7767D1A27860600FC4AA2 /* MXMemoryStore.h in Headers */, + 32FFB4F0217E146A00C96002 /* MXRecoveryKey.h in Headers */, 32CE6FB81A409B1F00317F1E /* MXFileStoreMetaData.h in Headers */, 32A31BC820D401FC005916C7 /* MXRoomFilter.h in Headers */, 322691361E5EFF8700966A6E /* MXDeviceListOperationsPool.h in Headers */, @@ -1268,6 +1368,7 @@ 323E0C5B1A306D7A00A31D73 /* MXEvent.h in Headers */, 327F8DB21C6112BA00581CA3 /* MXRoomThirdPartyInvite.h in Headers */, 32BA86AC21529E29008F277E /* MXRoomNameStringsLocalizable.h in Headers */, + 32BBAE742179CF4000D85F46 /* MXKeyBackup.h in Headers */, 92634B821EF2E3C400DB9F60 /* MXCallKitConfiguration.h in Headers */, 32618E7120ED2DF500E1D2EA /* MXFilterJSONModel.h in Headers */, 322360521A8E610500A3CA81 /* MXPushRuleDisplayNameCondtionChecker.h in Headers */, @@ -1288,6 +1389,7 @@ 32637ED41E5B00400011E20D /* MXDeviceList.h in Headers */, 32F9FA7D1DBA0CF0009D98A6 /* MXDecryptionResult.h in Headers */, 3245A7501AF7B2930001D8A7 /* MXCall.h in Headers */, + 02CAD43B217DD12F0074700B /* MXContentScanEncryptedBody.h in Headers */, 3271877D1DA7CB2F0071C818 /* MXDecrypting.h in Headers */, 32114A851A262CE000FF2EC4 /* MXStore.h in Headers */, 32BA86AF2152A79E008F277E /* MXRoomNameDefaultStringLocalizations.h in Headers */, @@ -1313,12 +1415,14 @@ 3271877B1DA7CAA60071C818 /* MXEncrypting.h in Headers */, 32A31BC420D3FFB0005916C7 /* MXFilter.h in Headers */, 32A151481DAF7C0C00400192 /* MXKey.h in Headers */, + 32BBAE6D2178E99100D85F46 /* MXKeyBackupVersion.h in Headers */, 32A151461DAF7C0C00400192 /* MXDeviceInfo.h in Headers */, 32CAB1071A91EA34008C5BB9 /* MXPushRuleRoomMemberCountConditionChecker.h in Headers */, C61A4CC41E5F38CB00442158 /* SwiftMatrixSDK.h in Headers */, 324BE4681E3FADB1008D99D4 /* MXMegolmExportEncryption.h in Headers */, 32CAB10B1A925B41008C5BB9 /* MXHTTPOperation.h in Headers */, 32F945F81FAB83D900622468 /* MXIncomingRoomKeyRequestCancellation.h in Headers */, + 02CAD438217DD12F0074700B /* MXContentScanResult.h in Headers */, 32618E7B20EFA45B00E1D2EA /* MXRoomMembers.h in Headers */, 3240969D1F9F751600DBA607 /* MXPushRuleSenderNotificationPermissionConditionChecker.h in Headers */, 32F634AB1FC5E3480054EF49 /* MXEventDecryptionResult.h in Headers */, @@ -1345,6 +1449,7 @@ F082946D1DB66C3D00CEAB63 /* MXInvite3PID.h in Headers */, 3233606F1A403A0D0071A488 /* MXFileStore.h in Headers */, 32A1513E1DAF768D00400192 /* MXOlmInboundGroupSession.h in Headers */, + 328BCB3321947BE200A976D3 /* MXKeyBackupVersionTrust.h in Headers */, 32FA10C11FA1C9EE00E54233 /* MXOutgoingRoomKeyRequestManager.h in Headers */, 3293C700214BBA4F009B3DDB /* MXPeekingRoomSummary.h in Headers */, 326056851C76FDF2009D44AD /* MXEventTimeline.h in Headers */, @@ -1359,9 +1464,12 @@ F0173EAC1FCF0E8900B5F6A3 /* MXGroup.h in Headers */, 329FB17F1A0B665800A5E88E /* MXUser.h in Headers */, 320DFDE219DD99B60068622A /* MXError.h in Headers */, + 32BBAE702178E99100D85F46 /* MXKeyBackupData.h in Headers */, 327E37B61A974F75007F026F /* MXLogger.h in Headers */, + 320A883C217F4E35002EA952 /* MXMegolmBackupCreationInfo.h in Headers */, 3256E3811DCB91EB003C9718 /* MXCryptoConstants.h in Headers */, 320DFDE619DD99B60068622A /* MXHTTPClient.h in Headers */, + 320A8840217F4E3F002EA952 /* MXMegolmBackupAuthData.h in Headers */, 320DFDDB19DD99B60068622A /* MXRoom.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1517,6 +1625,8 @@ 32A31BC520D3FFB0005916C7 /* MXFilter.m in Sources */, 32B76EA520FDE85100B095F6 /* MXRoomMembersCount.m in Sources */, F03EF4FF1DF014D9009DF592 /* MXMediaLoader.m in Sources */, + 320A8841217F4E3F002EA952 /* MXMegolmBackupAuthData.m in Sources */, + 32FFB4F1217E146A00C96002 /* MXRecoveryKey.m in Sources */, 32DC15D51A8CF874006F9AD3 /* MXPushRuleEventMatchConditionChecker.m in Sources */, 320BBF411D6C81550079890E /* MXEventsByTypesEnumeratorOnArray.m in Sources */, 3259CD541DF860C300186944 /* MXRealmCryptoStore.m in Sources */, @@ -1535,6 +1645,7 @@ C6D5D60A1E4FA74000706C0F /* MXEnumConstants.swift in Sources */, 3256E3821DCB91EB003C9718 /* MXCryptoConstants.m in Sources */, B18D18C22152B8E4003677D1 /* MXRoomMember.swift in Sources */, + 32BBAE6F2178E99100D85F46 /* MXKeyBackupVersion.m in Sources */, 326056861C76FDF2009D44AD /* MXEventTimeline.m in Sources */, 32A1513A1DAD292400400192 /* MXMegolmEncryption.m in Sources */, 32F945F71FAB83D900622468 /* MXIncomingRoomKeyRequest.m in Sources */, @@ -1543,6 +1654,7 @@ 3265CB391A14C43E00E24B2F /* MXRoomState.m in Sources */, 3281E8B819E42DFE00976E1A /* MXJSONModel.m in Sources */, 32A1514B1DAF7C0C00400192 /* MXUsersDevicesMap.m in Sources */, + 328BCB3421947BE200A976D3 /* MXKeyBackupVersionTrust.m in Sources */, C6B40F521F2A35BE00C42C72 /* MXRoomState.swift in Sources */, 32F945F51FAB83D900622468 /* MXIncomingRoomKeyRequestCancellation.m in Sources */, 32D776821A27877300FC4AA2 /* MXMemoryRoomStore.m in Sources */, @@ -1569,6 +1681,7 @@ 323E0C5C1A306D7A00A31D73 /* MXEvent.m in Sources */, F03EF5011DF014D9009DF592 /* MXMediaManager.m in Sources */, 32CAB10C1A925B41008C5BB9 /* MXHTTPOperation.m in Sources */, + 32BBAE6C2178E99100D85F46 /* MXKeyBackupData.m in Sources */, 3281E8BA19E42DFE00976E1A /* MXJSONModels.m in Sources */, 3245A7531AF7B2930001D8A7 /* MXCallManager.m in Sources */, 32E226A71D06AC9F00E6CA54 /* MXPeekingRoom.m in Sources */, @@ -1578,6 +1691,7 @@ 32A151471DAF7C0C00400192 /* MXDeviceInfo.m in Sources */, 32A1515C1DB525DA00400192 /* NSObject+sortedKeys.m in Sources */, 32618E7C20EFA45B00E1D2EA /* MXRoomMembers.m in Sources */, + 02CAD43A217DD12F0074700B /* MXContentScanEncryptedBody.m in Sources */, F03EF5091DF071D5009DF592 /* MXEncryptedAttachments.m in Sources */, B172857D2100D4F60052C51E /* MXSendReplyEventDefaultStringLocalizations.m in Sources */, 32FA10CF1FA1C9F700E54233 /* MXOutgoingRoomKeyRequest.m in Sources */, @@ -1596,11 +1710,13 @@ B17982F62119E4A2001FD722 /* MXRoomTombStoneContent.m in Sources */, C602B58E1F22A8D700B67D87 /* MXImage.swift in Sources */, 3295401A216385F100E300FC /* MXServerNoticeContent.m in Sources */, + 02CAD439217DD12F0074700B /* MXContentScanResult.m in Sources */, 32A151491DAF7C0C00400192 /* MXKey.m in Sources */, F0C34CBB1C18C93700C36F09 /* MXSDKOptions.m in Sources */, 320BBF441D6C81550079890E /* MXEventsEnumeratorOnArray.m in Sources */, 32618E7220ED2DF500E1D2EA /* MXFilterJSONModel.m in Sources */, 323F8865212D4E480001C73C /* MXMatrixVersions.m in Sources */, + 320A883D217F4E35002EA952 /* MXMegolmBackupCreationInfo.m in Sources */, 320DFDDC19DD99B60068622A /* MXRoom.m in Sources */, F08B8D5D1E014711006171A8 /* NSData+MatrixSDK.m in Sources */, 3291D4D51A68FFEB00C3BA41 /* MXFileRoomStore.m in Sources */, @@ -1613,6 +1729,7 @@ C6481AF21F1678A9000DB8A0 /* MXSessionEventListener.swift in Sources */, 32A1514F1DAF897600400192 /* MXOlmSessionResult.m in Sources */, C6F9357C1E5B39CA00FC34BF /* MXRestClient.swift in Sources */, + 32BBAE752179CF4000D85F46 /* MXKeyBackup.m in Sources */, 322A51B71D9AB15900C8536D /* MXCrypto.m in Sources */, B17982FB2119E4A2001FD722 /* MXRoomPredecessorInfo.m in Sources */, 32DC15D11A8CF7AE006F9AD3 /* MXNotificationCenter.m in Sources */, @@ -1655,6 +1772,7 @@ 321809B919EEBF3000377451 /* MXEventTests.m in Sources */, 32A31BC120D3F4C4005916C7 /* MXFilterTests.m in Sources */, C61A4AF41E5DD88400442158 /* Dummy.swift in Sources */, + 32935F61216FA49D00A1BC24 /* MXCryptoBackupTests.m in Sources */, 328DDEC11A07E57E008C7DC8 /* MXJSONModelTests.m in Sources */, 32832B5C1BCC048300241108 /* MXStoreFileStoreTests.m in Sources */, 320E1BC11E0AD674009635F5 /* MXRoomSummaryTests.m in Sources */, diff --git a/MatrixSDK/ContentScan/Data/MXContentScanEncryptedBody.h b/MatrixSDK/ContentScan/Data/MXContentScanEncryptedBody.h new file mode 100644 index 0000000000..a87410e211 --- /dev/null +++ b/MatrixSDK/ContentScan/Data/MXContentScanEncryptedBody.h @@ -0,0 +1,39 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXJSONModel.h" + +/** + `MXContentScanEncryptedBody` contains the encrypted body use to scan an encrypted content. + */ +@interface MXContentScanEncryptedBody : MXJSONModel + +/** + The base64-encoded string representing encrypted JSON of the original request body. + */ +@property (nonatomic) NSString *ciphertext; + +/** + The base64-encoded string representing the MAC. + */ +@property (nonatomic) NSString *mac; + +/** + The base64-encoded string representing the ephemeral public key. + */ +@property (nonatomic) NSString *ephemeral; + +@end diff --git a/MatrixSDK/ContentScan/Data/MXContentScanEncryptedBody.m b/MatrixSDK/ContentScan/Data/MXContentScanEncryptedBody.m new file mode 100644 index 0000000000..d4605835e9 --- /dev/null +++ b/MatrixSDK/ContentScan/Data/MXContentScanEncryptedBody.m @@ -0,0 +1,46 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXContentScanEncryptedBody.h" + +@implementation MXContentScanEncryptedBody + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXContentScanEncryptedBody *contentScanEncryptedBody = [[MXContentScanEncryptedBody alloc] init]; + if (contentScanEncryptedBody) + { + MXJSONModelSetString(contentScanEncryptedBody.ciphertext, JSONDictionary[@"ciphertext"]); + MXJSONModelSetString(contentScanEncryptedBody.mac, JSONDictionary[@"mac"]); + MXJSONModelSetString(contentScanEncryptedBody.ephemeral, JSONDictionary[@"ephemeral"]); + } + return contentScanEncryptedBody; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary]; + if (JSONDictionary) + { + JSONDictionary[@"ciphertext"] = _ciphertext; + JSONDictionary[@"mac"] = _mac; + JSONDictionary[@"ephemeral"] = _ephemeral; + } + + return JSONDictionary; +} + +@end diff --git a/MatrixSDK/ContentScan/JSONModels/MXContentScanResult.h b/MatrixSDK/ContentScan/JSONModels/MXContentScanResult.h new file mode 100644 index 0000000000..d14f364cc1 --- /dev/null +++ b/MatrixSDK/ContentScan/JSONModels/MXContentScanResult.h @@ -0,0 +1,34 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXJSONModel.h" + +/** + `MXContentScanResult` contains the antivirus scan result of a matrix content. + */ +@interface MXContentScanResult : MXJSONModel + +/** + If true, the script ran with an exit code of 0. Otherwise it ran with a non-zero exit code. + */ +@property (nonatomic) BOOL clean; + +/** + Human-readable information about the result. + */ +@property (nonatomic) NSString *info; + +@end diff --git a/MatrixSDK/ContentScan/JSONModels/MXContentScanResult.m b/MatrixSDK/ContentScan/JSONModels/MXContentScanResult.m new file mode 100644 index 0000000000..9bd4b15859 --- /dev/null +++ b/MatrixSDK/ContentScan/JSONModels/MXContentScanResult.m @@ -0,0 +1,32 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXContentScanResult.h" + +@implementation MXContentScanResult + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXContentScanResult *contentScanResult = [[MXContentScanResult alloc] init]; + if (contentScanResult) + { + MXJSONModelSetBoolean(contentScanResult.clean, JSONDictionary[@"clean"]); + MXJSONModelSetString(contentScanResult.info, JSONDictionary[@"info"]); + } + return contentScanResult; +} + +@end diff --git a/MatrixSDK/Crypto/Algorithms/MXDecrypting.h b/MatrixSDK/Crypto/Algorithms/MXDecrypting.h index e6d843bc7e..ab5e34fe06 100644 --- a/MatrixSDK/Crypto/Algorithms/MXDecrypting.h +++ b/MatrixSDK/Crypto/Algorithms/MXDecrypting.h @@ -59,9 +59,11 @@ /** Import a room key. + @param backUp YES to back up them to the homeserver. @param session the session data to import. + @return YES if the key has been imported. */ -- (void)importRoomKey:(MXMegolmSessionData*)session; +- (BOOL)importRoomKey:(MXMegolmSessionData*)session backUp:(BOOL)backUp; /** Determine if we have the keys necessary to respond to a room key request. diff --git a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m index d5331fb1c5..2dcb9bb731 100644 --- a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m +++ b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmDecryption.m @@ -227,6 +227,8 @@ - (void)onRoomKeyEvent:(MXEvent *)event [olmDevice addInboundGroupSession:sessionId sessionKey:sessionKey roomId:roomId senderKey:senderKey forwardingCurve25519KeyChain:forwardingKeyChain keysClaimed:keysClaimed exportFormat:exportFormat]; + [crypto.backup maybeSendKeyBackup]; + // cancel any outstanding room key requests for this session [crypto cancelRoomKeyRequest:@{ @"algorithm": content[@"algorithm"], @@ -238,12 +240,26 @@ - (void)onRoomKeyEvent:(MXEvent *)event [self retryDecryption:senderKey sessionId:content[@"session_id"]]; } -- (void)importRoomKey:(MXMegolmSessionData *)session +- (BOOL)importRoomKey:(MXMegolmSessionData *)session backUp:(BOOL)backUp { - [olmDevice importInboundGroupSession:session]; + BOOL imported = [olmDevice importInboundGroupSession:session]; + if (imported) + { + // Do not back up the key if it comes from a backup recovery + if (backUp) + { + [crypto.backup maybeSendKeyBackup]; + } + else + { + [crypto.store markBackupDoneForInboundGroupSessionWithId:session.sessionId andSenderKey:session.senderKey]; + } + + // Have another go at decrypting events sent with this session + [self retryDecryption:session.senderKey sessionId:session.sessionId]; + } - // Have another go at decrypting events sent with this session - [self retryDecryption:session.senderKey sessionId:session.sessionId]; + return imported; } - (BOOL)hasKeysForKeyRequest:(MXIncomingRoomKeyRequest*)keyRequest diff --git a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m index a7804f9223..aa86bc4c76 100644 --- a/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m +++ b/MatrixSDK/Crypto/Algorithms/Megolm/MXMegolmEncryption.m @@ -344,6 +344,8 @@ - (MXOutboundSessionInfo*)prepareNewSession exportFormat:NO ]; + [crypto.backup maybeSendKeyBackup]; + return [[MXOutboundSessionInfo alloc] initWithSessionID:sessionId]; } diff --git a/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m b/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m index 8f2889ecc1..fcf652d498 100644 --- a/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m +++ b/MatrixSDK/Crypto/Algorithms/Olm/MXOlmDecryption.m @@ -236,9 +236,10 @@ - (void)onRoomKeyEvent:(MXEvent *)event // No impact for olm } -- (void)importRoomKey:(MXMegolmSessionData *)session +- (BOOL)importRoomKey:(MXMegolmSessionData *)session backUp:(BOOL)backUp { // No impact for olm + return NO; } - (BOOL)hasKeysForKeyRequest:(MXIncomingRoomKeyRequest*)keyRequest diff --git a/MatrixSDK/Crypto/Data/MXCryptoConstants.h b/MatrixSDK/Crypto/Data/MXCryptoConstants.h index 51cdc54d65..b9d60ffeed 100644 --- a/MatrixSDK/Crypto/Data/MXCryptoConstants.h +++ b/MatrixSDK/Crypto/Data/MXCryptoConstants.h @@ -27,6 +27,11 @@ FOUNDATION_EXPORT NSString *const kMXCryptoOlmAlgorithm; */ FOUNDATION_EXPORT NSString *const kMXCryptoMegolmAlgorithm; +/** + Matrix algorithm tag for megolm keys backup. + */ +FOUNDATION_EXPORT NSString *const kMXCryptoMegolmBackupAlgorithm; + #pragma mark - Encrypting error @@ -47,3 +52,13 @@ FOUNDATION_EXPORT NSString* const MXEncryptingErrorUnknownDeviceReason; */ FOUNDATION_EXPORT NSString *const MXEncryptingErrorUnknownDeviceDevicesKey; + +#pragma mark - Backup error + +FOUNDATION_EXPORT NSString *const MXKeyBackupErrorDomain; + +typedef enum : NSUInteger +{ + MXKeyBackupErrorInvalidStateCode, + MXKeyBackupErrorInvalidParametersCode +} MXKeyBackupErrorCode; diff --git a/MatrixSDK/Crypto/Data/MXCryptoConstants.m b/MatrixSDK/Crypto/Data/MXCryptoConstants.m index 7fa6d1f2eb..c23ea8dd45 100644 --- a/MatrixSDK/Crypto/Data/MXCryptoConstants.m +++ b/MatrixSDK/Crypto/Data/MXCryptoConstants.m @@ -17,8 +17,9 @@ #import "MXCryptoConstants.h" -NSString *const kMXCryptoOlmAlgorithm = @"m.olm.v1.curve25519-aes-sha2"; -NSString *const kMXCryptoMegolmAlgorithm = @"m.megolm.v1.aes-sha2"; +NSString *const kMXCryptoOlmAlgorithm = @"m.olm.v1.curve25519-aes-sha2"; +NSString *const kMXCryptoMegolmAlgorithm = @"m.megolm.v1.aes-sha2"; +NSString *const kMXCryptoMegolmBackupAlgorithm = @"m.megolm_backup.v1.curve25519-aes-sha2"; #pragma mark - Encrypting error @@ -28,3 +29,8 @@ NSString* const MXEncryptingErrorUnknownDeviceReason = @"This room contains unknown devices which have not been verified. We strongly recommend you verify them before continuing."; NSString* const MXEncryptingErrorUnknownDeviceDevicesKey = @"MXEncryptingErrorUnknownDeviceDevicesKey"; + + +#pragma mark - Backup error + +NSString *const MXKeyBackupErrorDomain = @"MXKeyBackupErrorDomain"; diff --git a/MatrixSDK/Crypto/Data/MXDeviceList.h b/MatrixSDK/Crypto/Data/MXDeviceList.h index b364e5c802..d733dcb755 100644 --- a/MatrixSDK/Crypto/Data/MXDeviceList.h +++ b/MatrixSDK/Crypto/Data/MXDeviceList.h @@ -124,12 +124,11 @@ typedef enum : NSUInteger /** Find a device by curve25519 identity key - @param userId the owner of the device. @param algorithm the encryption algorithm. @param senderKey the curve25519 key to match. @return the device info. */ -- (MXDeviceInfo*)deviceWithIdentityKey:(NSString*)senderKey forUser:(NSString*)userId andAlgorithm:(NSString*)algorithm; +- (MXDeviceInfo*)deviceWithIdentityKey:(NSString*)senderKey andAlgorithm:(NSString*)algorithm; /** Flag the given user for device-list tracking, if they are not already. diff --git a/MatrixSDK/Crypto/Data/MXDeviceList.m b/MatrixSDK/Crypto/Data/MXDeviceList.m index 1b84ca352f..f5be8af436 100644 --- a/MatrixSDK/Crypto/Data/MXDeviceList.m +++ b/MatrixSDK/Crypto/Data/MXDeviceList.m @@ -226,7 +226,7 @@ - (MXDeviceInfo*)storedDevice:(NSString*)userId deviceId:(NSString*)deviceId return [crypto.store devicesForUser:userId][deviceId]; } -- (MXDeviceInfo *)deviceWithIdentityKey:(NSString *)senderKey forUser:(NSString *)userId andAlgorithm:(NSString *)algorithm +- (MXDeviceInfo *)deviceWithIdentityKey:(NSString *)senderKey andAlgorithm:(NSString *)algorithm { if (![algorithm isEqualToString:kMXCryptoOlmAlgorithm] && ![algorithm isEqualToString:kMXCryptoMegolmAlgorithm]) @@ -235,23 +235,7 @@ - (MXDeviceInfo *)deviceWithIdentityKey:(NSString *)senderKey forUser:(NSString return nil; } - for (MXDeviceInfo *device in [self storedDevicesForUser:userId]) - { - for (NSString *keyId in device.keys) - { - if ([keyId hasPrefix:@"curve25519:"]) - { - NSString *deviceKey = device.keys[keyId]; - if ([senderKey isEqualToString:deviceKey]) - { - return device; - } - } - } - } - - // Doesn't match a known device - return nil; + return [crypto.store deviceWithIdentityKey:senderKey]; } - (void)startTrackingDeviceList:(NSString*)userId diff --git a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h index d66a25a26a..387214bec1 100644 --- a/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h +++ b/MatrixSDK/Crypto/Data/Store/MXCryptoStore.h @@ -115,7 +115,7 @@ /** Store a device for a user. - @param userId The user's id. + @param userId the user's id. @param device the device to store. */ - (void)storeDeviceForUser:(NSString*)userId device:(MXDeviceInfo*)device; @@ -123,12 +123,20 @@ /** Retrieve a device for a user. - @param deviceId The device id. - @param userId The user's id. - @return A map from device id to 'MXDevice' object for the device. + @param deviceId the device id. + @param userId the user's id. + @return The device. */ - (MXDeviceInfo*)deviceWithDeviceId:(NSString*)deviceId forUser:(NSString*)userId; +/** + Retrieve a device by its identity key. + + @param identityKey the device identity key (`MXDeviceInfo.identityKey`)/ + @return The device. + */ +- (MXDeviceInfo*)deviceWithIdentityKey:(NSString*)identityKey; + /** Store the known devices for a user. @@ -215,6 +223,44 @@ - (NSArray *)inboundGroupSessions; +#pragma mark - Key backup + +/** + The backup version currently used. + Nil means no backup. + */ +@property (nonatomic) NSString *backupVersion; + +/** + Mark all inbound group sessions as not backed up. + */ +- (void)resetBackupMarkers; + +/** + Mark an inbound group session as backed up on the user homeserver. + + @param sessionId the session identifier. + @param senderKey the base64-encoded curve25519 key of the sender. + */ +- (void)markBackupDoneForInboundGroupSessionWithId:(NSString*)sessionId andSenderKey:(NSString*)senderKey; + +/** + Retrieve inbound group sessions that are not yet backed up. + + @param limit the maximum number of sessions to return. + @return an array of non backed up inbound group sessions. + */ +- (NSArray*)inboundGroupSessionsToBackup:(NSUInteger)limit; + +/** + Number of stored inbound group sessions. + + @param onlyBackedUp if YES, count only session marked as backed up. + @return a count. + */ +- (NSUInteger)inboundGroupSessionsCount:(BOOL)onlyBackedUp; + + #pragma mark - Key sharing - Outgoing key requests /** diff --git a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m index 9893a0fab9..240fee2628 100644 --- a/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m +++ b/MatrixSDK/Crypto/Data/Store/MXRealmCryptoStore/MXRealmCryptoStore.m @@ -23,7 +23,7 @@ #import "MXSession.h" #import "MXTools.h" -NSUInteger const kMXRealmCryptoStoreVersion = 6; +NSUInteger const kMXRealmCryptoStoreVersion = 7; static NSString *const kMXRealmCryptoStoreFolder = @"MXRealmCryptoStore"; @@ -33,6 +33,7 @@ @interface MXRealmDeviceInfo : RLMObject @property NSData *deviceInfoData; @property (nonatomic) NSString *deviceId; +@property (nonatomic) NSString *identityKey; @end @implementation MXRealmDeviceInfo @@ -95,9 +96,25 @@ @interface MXRealmOlmInboundGroupSession : RLMObject @property NSString *sessionId; @property NSString *senderKey; @property NSData *olmInboundGroupSessionData; + +// A primary key is required to update `backedUp`. +// Do our combined primary key ourselves as it is not supported by Realm. +@property NSString *sessionIdSenderKey; + +// Indicate if the key has been backed up to the homeserver +@property BOOL backedUp; @end @implementation MXRealmOlmInboundGroupSession ++ (NSString *)primaryKey +{ + return @"sessionIdSenderKey"; +} + ++ (NSString *)primaryKeyWithSessionId:(NSString*)sessionId senderKey:(NSString*)senderKey +{ + return [NSString stringWithFormat:@"%@|%@", sessionId, senderKey]; +} @end RLM_ARRAY_TYPE(MXRealmOlmInboundGroupSession) @@ -134,6 +151,12 @@ @interface MXRealmOlmAccount : RLMObject Settings for blacklisting unverified devices. */ @property (nonatomic) BOOL globalBlacklistUnverifiedDevices; + +/** + The backup version currently used. + */ +@property (nonatomic) NSString *backupVersion; + @end @implementation MXRealmOlmAccount @@ -397,6 +420,7 @@ - (void)storeDeviceForUser:(NSString*)userID device:(MXDeviceInfo*)device @"deviceId": device.deviceId, @"deviceInfoData": [NSKeyedArchiver archivedDataWithRootObject:device] }]; + realmDevice.identityKey = device.identityKey; [realmUser.devices addObject:realmDevice]; } else @@ -422,6 +446,17 @@ - (MXDeviceInfo*)deviceWithDeviceId:(NSString*)deviceId forUser:(NSString*)userI return nil; } +- (MXDeviceInfo*)deviceWithIdentityKey:(NSString*)identityKey +{ + MXRealmDeviceInfo *realmDevice = [MXRealmDeviceInfo objectsInRealm:self.realm where:@"identityKey = %@", identityKey].firstObject; + if (realmDevice) + { + return [NSKeyedUnarchiver unarchiveObjectWithData:realmDevice.deviceInfoData]; + } + + return nil; +} + - (void)storeDevicesForUser:(NSString*)userID devices:(NSDictionary*)devices { NSDate *startDate = [NSDate date]; @@ -451,6 +486,7 @@ - (void)storeDevicesForUser:(NSString*)userID devices:(NSDictionary %@", sessionId, realmSession ? @"found" : @"not found"); @@ -696,6 +739,93 @@ - (void)removeInboundGroupSessionWithId:(NSString*)sessionId andSenderKey:(NSStr }]; } + +#pragma mark - Key backup + +- (void)setBackupVersion:(NSString *)backupVersion +{ + MXRealmOlmAccount *account = self.accountInCurrentThread; + [account.realm transactionWithBlock:^{ + account.backupVersion = backupVersion; + }]; +} + +- (NSString *)backupVersion +{ + MXRealmOlmAccount *account = self.accountInCurrentThread; + return account.backupVersion; +} + +- (void)resetBackupMarkers +{ + RLMRealm *realm = self.realm; + + RLMResults *realmSessions = [MXRealmOlmInboundGroupSession allObjectsInRealm:realm]; + + [realm transactionWithBlock:^{ + for (MXRealmOlmInboundGroupSession *realmSession in realmSessions) + { + realmSession.backedUp = NO; + } + + [realm addOrUpdateObjects:realmSessions]; + }]; +} + +- (void)markBackupDoneForInboundGroupSessionWithId:(NSString*)sessionId andSenderKey:(NSString*)senderKey +{ + RLMRealm *realm = self.realm; + + NSString *sessionIdSenderKey = [MXRealmOlmInboundGroupSession primaryKeyWithSessionId:sessionId + senderKey:senderKey]; + MXRealmOlmInboundGroupSession *realmSession = [MXRealmOlmInboundGroupSession objectsInRealm:realm where:@"sessionIdSenderKey = %@", sessionIdSenderKey].firstObject; + + [realm transactionWithBlock:^{ + realmSession.backedUp = @(YES); + + [realm addOrUpdateObject:realmSession]; + }]; +} + +- (NSArray*)inboundGroupSessionsToBackup:(NSUInteger)limit +{ + NSMutableArray *sessions = [NSMutableArray new]; + + RLMRealm *realm = self.realm; + + RLMResults *realmSessions = [MXRealmOlmInboundGroupSession objectsInRealm:realm where:@"backedUp = NO"]; + + for (MXRealmOlmInboundGroupSession *realmSession in realmSessions) + { + MXOlmInboundGroupSession *session = [NSKeyedUnarchiver unarchiveObjectWithData:realmSession.olmInboundGroupSessionData]; + [sessions addObject:session]; + + if (sessions.count >= limit) + { + break; + } + } + + return sessions; +} + +- (NSUInteger)inboundGroupSessionsCount:(BOOL)onlyBackedUp +{ + RLMRealm *realm = self.realm; + RLMResults *realmSessions; + + if (onlyBackedUp) + { + realmSessions = [MXRealmOlmInboundGroupSession objectsInRealm:realm where:@"backedUp = YES"]; + } + else + { + realmSessions = [MXRealmOlmInboundGroupSession allObjectsInRealm:realm]; + } + + return realmSessions.count; +} + #pragma mark - Key sharing - Outgoing key requests - (MXOutgoingRoomKeyRequest*)outgoingRoomKeyRequestWithRequestBody:(NSDictionary *)requestBody @@ -860,10 +990,26 @@ + (RLMRealm*)realmForUser:(NSString*)userId // will be called twice for the same room id which breaks the uniqueness of the // primary key (roomId) for this table. RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; - + + NSURL *defaultRealmPathURL = config.fileURL.URLByDeletingLastPathComponent; + +#if TARGET_OS_SIMULATOR + // On simulator from iOS 11, the Documents folder used by Realm by default + // can be missing. Create it if required + // https://stackoverflow.com/a/50817364 + if (![NSFileManager.defaultManager fileExistsAtPath:defaultRealmPathURL.path]) + { + NSError *error; + [[NSFileManager defaultManager] createDirectoryAtPath:defaultRealmPathURL.path + withIntermediateDirectories:YES + attributes:nil + error:&error]; + NSLog(@"[MXRealmCryptoStore] On simulator, create the file tree used by Realm. Error: %@", error); + } +#endif + // Default db file URL: use the default directory, but replace the filename with the userId. - NSURL *defaultRealmFileURL = [[[config.fileURL URLByDeletingLastPathComponent] - URLByAppendingPathComponent:userId] + NSURL *defaultRealmFileURL = [[defaultRealmPathURL URLByAppendingPathComponent:userId] URLByAppendingPathExtension:@"realm"]; // Check for a potential application group id. @@ -1004,23 +1150,43 @@ + (RLMRealm*)realmForUser:(NSString*)userId } case 2: - { NSLog(@"[MXRealmCryptoStore] Migration from schema #2 -> #3: Nothing to do (add MXRealmOlmAccount.deviceSyncToken)"); - } case 3: - { NSLog(@"[MXRealmCryptoStore] Migration from schema #3 -> #4: Nothing to do (add MXRealmOlmAccount.globalBlacklistUnverifiedDevices & MXRealmRoomAlgortithm.blacklistUnverifiedDevices)"); - } case 4: - { NSLog(@"[MXRealmCryptoStore] Migration from schema #4 -> #5: Nothing to do (add deviceTrackingStatusData)"); - } case 5: - { NSLog(@"[MXRealmCryptoStore] Migration from schema #5 -> #6: Nothing to do (remove MXRealmOlmAccount.deviceAnnounced)"); + + case 6: + { + NSLog(@"[MXRealmCryptoStore] Migration from schema #6 -> #7"); + + // We need to update the db because a sessionId property has been added to MXRealmOlmSession + // to ensure uniqueness + NSLog(@" Add sessionIdSenderKey, a combined primary key, to all MXRealmOlmInboundGroupSession objects"); + [migration enumerateObjects:MXRealmOlmInboundGroupSession.className block:^(RLMObject *oldObject, RLMObject *newObject) { + + newObject[@"sessionIdSenderKey"] = [MXRealmOlmInboundGroupSession primaryKeyWithSessionId:oldObject[@"sessionId"] + senderKey:oldObject[@"senderKey"]]; + }]; + + // We need to update the db because a identityKey property has been added to MXRealmDeviceInfo + NSLog(@" Add identityKey to all MXRealmOlmInboundGroupSession objects"); + [migration enumerateObjects:MXRealmDeviceInfo.className block:^(RLMObject *oldObject, RLMObject *newObject) { + + MXDeviceInfo *device = [NSKeyedUnarchiver unarchiveObjectWithData:oldObject[@"deviceInfoData"]]; + NSString *identityKey = device.identityKey; + if (identityKey) + { + newObject[@"identityKey"] = identityKey; + } + }]; + + NSLog(@"[MXRealmCryptoStore] Migration from schema #6 -> #7 completed"); } } } diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupData.h b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupData.h new file mode 100644 index 0000000000..f5663e6109 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupData.h @@ -0,0 +1,75 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXJSONModel.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Backup data for one key. + */ +@interface MXKeyBackupData : MXJSONModel + +/** + The index of the first message in the session that the key can decrypt. + */ +@property (nonatomic) NSInteger firstMessageIndex; + +/** + The number of times this key has been forwarded. + */ +@property (nonatomic) NSInteger forwardedCount; + +/** + Whether the device backing up the key has verified the device that the key is from. + */ +@property (nonatomic) BOOL verified; + +/** + Algorithm-dependent data. + */ +@property (nonatomic) NSDictionary *sessionData; + +@end + +/** + Backup data for several keys within a room. + */ +@interface MXRoomKeysBackupData : MXJSONModel + +/** + + sessionId -> MXKeyBackupData + */ +@property (nonatomic) NSDictionary *sessions; + +@end + +/** + Backup data for several keys in several rooms. + */ +@interface MXKeysBackupData : MXJSONModel + +/** + roomId -> MXRoomKeysBackupData + */ +@property (nonatomic) NSDictionary *rooms; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupData.m b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupData.m new file mode 100644 index 0000000000..09ab02418e --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupData.m @@ -0,0 +1,142 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXKeyBackupData.h" + +@implementation MXKeyBackupData + +#pragma mark - MXJSONModel + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXKeyBackupData *keyBackupData = [[MXKeyBackupData alloc] init]; + if (keyBackupData) + { + MXJSONModelSetInteger(keyBackupData.firstMessageIndex, JSONDictionary[@"first_message_index"]); + MXJSONModelSetInteger(keyBackupData.forwardedCount, JSONDictionary[@"forwarded_count"]); + MXJSONModelSetBoolean(keyBackupData.verified, JSONDictionary[@"is_verified"]); + MXJSONModelSetDictionary(keyBackupData.sessionData, JSONDictionary[@"session_data"]); + } + return keyBackupData; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary]; + + JSONDictionary[@"first_message_index"] = @(_firstMessageIndex); + JSONDictionary[@"forwarded_count"] = @(_forwardedCount); + JSONDictionary[@"is_verified"] = @(_verified); + JSONDictionary[@"session_data"] = _sessionData; + + return JSONDictionary; +} + +@end + + +@implementation MXRoomKeysBackupData + +#pragma mark - MXJSONModel + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXRoomKeysBackupData *roomKeysBackupData = [[MXRoomKeysBackupData alloc] init]; + if (roomKeysBackupData) + { + NSDictionary *sessions; + MXJSONModelSetDictionary(sessions, JSONDictionary[@"sessions"]); + + if (sessions) + { + NSMutableDictionary *mutableSessions = [[NSMutableDictionary alloc] initWithCapacity:sessions.count]; + for (NSString *sessionId in sessions) + { + MXKeyBackupData *keyBackupData; + MXJSONModelSetMXJSONModel(keyBackupData, MXKeyBackupData, sessions[sessionId]); + if (keyBackupData) + { + mutableSessions[sessionId] = keyBackupData; + } + } + roomKeysBackupData.sessions = mutableSessions; + } + } + return roomKeysBackupData; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *sessions = [NSMutableDictionary dictionary]; + + for (NSString *sessionId in _sessions) + { + sessions[sessionId] = _sessions[sessionId].JSONDictionary; + } + + return @{ + @"sessions" : sessions + }; +} + +@end + + +@implementation MXKeysBackupData + +#pragma mark - MXJSONModel + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXKeysBackupData *keysBackupData = [[MXKeysBackupData alloc] init]; + if (keysBackupData) + { + NSDictionary *rooms; + MXJSONModelSetDictionary(rooms, JSONDictionary[@"rooms"]); + + if (rooms) + { + NSMutableDictionary *mutableRooms = [[NSMutableDictionary alloc] initWithCapacity:rooms.count]; + for (NSString *roomId in rooms) + { + MXRoomKeysBackupData *roomKeysBackupData; + MXJSONModelSetMXJSONModel(roomKeysBackupData, MXRoomKeysBackupData, rooms[roomId]); + if (roomKeysBackupData) + { + mutableRooms[roomId] = roomKeysBackupData; + } + } + keysBackupData.rooms = mutableRooms; + } + } + return keysBackupData; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *rooms = [NSMutableDictionary dictionary]; + + for (NSString *roomId in _rooms) + { + rooms[roomId] = _rooms[roomId].JSONDictionary; + } + + return @{ + @"rooms" : rooms + }; +} + +@end diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersion.h b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersion.h new file mode 100644 index 0000000000..e03f2380ef --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersion.h @@ -0,0 +1,53 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXJSONModel.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Information on a backup version. + */ +@interface MXKeyBackupVersion : MXJSONModel + +/** + The algorithm used for storing backups. + Currently, only kMXCryptoMegolmBackupAlgorithm (m.megolm_backup.v1.curve25519-aes-sha2) is defined. + */ +@property (nonatomic) NSString *algorithm; + +/** + Algorithm-dependent data. + */ +@property (nonatomic) NSDictionary *authData; + +/** + The backup version. + */ +@property (nonatomic, nullable) NSString *version; + +/** + Enforce usage of factory method to guarantee non-nullabity. + TODO: Should be done at `MXJSONModel` model. + */ +//- (instancetype)init NS_UNAVAILABLE; +//+ (instancetype)new NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersion.m b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersion.m new file mode 100644 index 0000000000..604e82cd10 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersion.m @@ -0,0 +1,66 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXKeyBackupVersion.h" + +@implementation MXKeyBackupVersion + +- (nullable instancetype)initWithJSON:(NSDictionary *)JSONDictionary +{ + self = [super init]; + if (self) + { + MXJSONModelSetString(_algorithm, JSONDictionary[@"algorithm"]); + MXJSONModelSetDictionary(_authData, JSONDictionary[@"auth_data"]); + MXJSONModelSetString(_version, JSONDictionary[@"version"]); + } + + // nonnull checks + if (!_algorithm || !_authData) + { + return nil; + } + + return self; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@" version: %@ - algorithm: %@", self, _version, _algorithm]; +} + +#pragma mark - MXJSONModel + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + return [[MXKeyBackupVersion alloc] initWithJSON:JSONDictionary]; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary]; + + JSONDictionary[@"algorithm"] = _algorithm; + JSONDictionary[@"auth_data"] = _authData; + if (_version) + { + JSONDictionary[@"version"] = _version; + } + + return JSONDictionary; +} + +@end diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h new file mode 100644 index 0000000000..bb0fb5e4cb --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.h @@ -0,0 +1,62 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXDeviceInfo.h" + +NS_ASSUME_NONNULL_BEGIN + +@class MXKeyBackupVersionTrustSignature; + + +/** + Data model for response to [MXKeyBackup isKeyBackupTrusted:]. + */ +@interface MXKeyBackupVersionTrust : NSObject + +/** + Flag to indicate if the backup is trusted. + YES if there is a signature that is valid & from a trusted device. + */ +@property (nonatomic) BOOL usable; + +/** + Signatures found in the backup version. + */ +@property (nonatomic) NSArray *signatures; + +@end + + +/** + A signature in a the `MXKeyBackupVersionTrust` object. + */ +@interface MXKeyBackupVersionTrustSignature : NSObject + +/** + The device that signed the backup version. + */ +@property (nonatomic) MXDeviceInfo *device; + +/** + Flag to indicate the signature from this device is valid. + */ +@property (nonatomic) BOOL valid; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m new file mode 100644 index 0000000000..44981fe2d8 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXKeyBackupVersionTrust.m @@ -0,0 +1,36 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXKeyBackupVersionTrust.h" + +@implementation MXKeyBackupVersionTrust + +- (instancetype)init +{ + self = [super init]; + if (self) + { + _usable = NO; + _signatures = [NSArray new]; + } + return self; +} + +@end + +@implementation MXKeyBackupVersionTrustSignature + +@end diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupAuthData.h b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupAuthData.h new file mode 100644 index 0000000000..8c706a72d9 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupAuthData.h @@ -0,0 +1,46 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXJSONModel.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + Data model for MXKeyBackupVersion.authData in case of kMXCryptoMegolmBackupAlgorithm. + */ +@interface MXMegolmBackupAuthData : MXJSONModel + +/** + The curve25519 public key used to encrypt the backups. + */ +@property (nonatomic) NSString *publicKey; + +/** + Signatures of the public key. + */ +@property (nonatomic) NSDictionary *signatures; + +/** + Same as the parent [MXJSONModel JSONDictionary] but return only + data that must be signed. + */ +@property (nonatomic, readonly) NSDictionary *signalableJSONDictionary; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupAuthData.m b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupAuthData.m new file mode 100644 index 0000000000..33fcb21ba2 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupAuthData.m @@ -0,0 +1,56 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXMegolmBackupAuthData.h" + +@implementation MXMegolmBackupAuthData + +#pragma mark - MXJSONModel + ++ (id)modelFromJSON:(NSDictionary *)JSONDictionary +{ + MXMegolmBackupAuthData *megolmBackupAuthData = [MXMegolmBackupAuthData new]; + if (megolmBackupAuthData) + { + MXJSONModelSetString(megolmBackupAuthData.publicKey, JSONDictionary[@"public_key"]); + MXJSONModelSetDictionary(megolmBackupAuthData.signatures, JSONDictionary[@"signatures"]); + } + + return megolmBackupAuthData; +} + +- (NSDictionary *)JSONDictionary +{ + NSMutableDictionary *JSONDictionary = [NSMutableDictionary dictionary]; + + JSONDictionary[@"public_key"] = _publicKey; + + if (_signatures) + { + JSONDictionary[@"signatures"] = _signatures; + } + + return JSONDictionary; +} + +- (NSDictionary *)signalableJSONDictionary +{ + return @{ + @"public_key": _publicKey + }; +} + +@end diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupCreationInfo.h b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupCreationInfo.h new file mode 100644 index 0000000000..334c337005 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupCreationInfo.h @@ -0,0 +1,46 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXMegolmBackupAuthData.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + `MXMegolmBackupCreationInfo` represents data to create a megolm keys backup on + the homeserver. + */ +@interface MXMegolmBackupCreationInfo : NSObject + +/** + The algorithm used for storing backups (kMXCryptoMegolmBackupAlgorithm). + */ +@property (nonatomic) NSString *algorithm; + +/** + Authentication data. + */ +@property (nonatomic) MXMegolmBackupAuthData *authData; + +/** + The Base58 recovery key. + */ +@property (nonatomic) NSString *recoveryKey; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupCreationInfo.m b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupCreationInfo.m new file mode 100644 index 0000000000..6210f95ea7 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/Data/MXMegolmBackupCreationInfo.m @@ -0,0 +1,21 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXMegolmBackupCreationInfo.h" + +@implementation MXMegolmBackupCreationInfo + +@end diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h new file mode 100644 index 0000000000..0c6caa434a --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.h @@ -0,0 +1,253 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MXRestClient.h" +#import "MXMegolmBackupCreationInfo.h" +#import "MXKeyBackupVersionTrust.h" + +@class MXSession; +@class OLMPkEncryption; + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Constants definitions + +/** + * E2e keys backup states. + * + * | + * V deleteKeyBackupVersion (on current backup) + * +----------------------> UNKNOWN <------------- + * | | + * | | checkAndStartKeyBackup (at startup or on new verified device or a new detected backup) + * | V + * | CHECKING BACKUP + * | | + * | Network error | + * +<----------+----------------+---------> DISABLED <----------------------+ + * | | | | | + * | | | | createKeyBackupVersion | + * | V | V | + * +<--- WRONG VERSION | ENABLING | + * ^ | | | + * | V | error | + * | READY <--------+----------------------------+ + * | | | + * | | on new key | + * | V | + * | WILL BACK UP | + * | | | + * | V | + * | BACKING UP | + * | Error | | + * +<---------------+------------->+ + * + */ +typedef enum : NSUInteger +{ + // Need to check the current backup version on the homeserver + MXKeyBackupStateUnknown = 0, + + // Making the check request on the homeserver + MXKeyBackupStateCheckingBackUpOnHomeserver, + + // Backup has been stopped because a new backup version has been detected on + // the homeserver + MXKeyBackupStateWrongBackUpVersion, + + // Backup from this device is not enabled + MXKeyBackupStateDisabled, + + // Backup is being enabled + // The backup version is being created on the homeserver + MXKeyBackupStateEnabling, + + // Backup is enabled and ready to send backup to the homeserver + MXKeyBackupStateReadyToBackUp, + + // Backup is going to be send to the homeserver + MXKeyBackupStateWillBackUp, + + // Backup is being sent to the homeserver + MXKeyBackupStateBackingUp +} MXKeyBackupState; + +/** + Posted when the state of the MXKeyBackup instance changes. + */ +FOUNDATION_EXPORT NSString *const kMXKeyBackupDidStateChangeNotification; + + +/** + A `MXKeyBackup` class instance manage incremental backup of e2e keys (megolm keys) + to the user's homeserver. + */ +@interface MXKeyBackup : NSObject + +#pragma mark - Backup management + +/** + Get information about the current backup version defined on the homeserver. + + It can be different than `self.keyBackupVersion`. + + @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*)version:(void (^)(MXKeyBackupVersion * _Nullable keyBackupVersion))success + failure:(void (^)(NSError *error))failure; + +/** + Check trust on a key backup version. + + @param keyBackupVersion the backup version to check. + @param onComplete block called when the operations completes. + */ +- (void)isKeyBackupTrusted:(MXKeyBackupVersion*)keyBackupVersion onComplete:(void (^)(MXKeyBackupVersionTrust *keyBackupVersionTrust))onComplete; + +/** + Set up the data required to create a new backup version. + + The backup version will not be created and enabled until `createKeyBackupVersion` + is called. + The returned `MXMegolmBackupCreationInfo` object has a `recoveryKey` member with + the user-facing recovery key string. + + @param success A block object called when the operation succeeds. + @param failure A block object called when the operation fails + */ +- (void)prepareKeyBackupVersion:(void (^)(MXMegolmBackupCreationInfo *keyBackupCreationInfo))success + failure:(nullable void (^)(NSError *error))failure; + +/** + Create a new key backup version and enable it, using the information return from +`prepareKeyBackupVersion`. + + @param keyBackupCreationInfo the info object from `prepareKeyBackupVersion`. + + @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*)createKeyBackupVersion:(MXMegolmBackupCreationInfo*)keyBackupCreationInfo + success:(void (^)(MXKeyBackupVersion *keyBackupVersion))success + failure:(nullable void (^)(NSError *error))failure; + +/** + Delete a key backup version. + + If we are backing up to this version. Backup will be stopped. + + @param version the backup version to delete. + + @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*)deleteKeyBackupVersion:(NSString*)version + success:(void (^)(void))success + failure:(nullable void (^)(NSError *error))failure; + + +#pragma mark - Backup storing + +/** + Start to back up keys immediately. + + @param success A block object called when the operation complets. + @param progress A block object called to indicate operation progress based on number of backed up keys. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (void)backupAllGroupSessions:(nullable void (^)(void))success + progress:(nullable void (^)(NSProgress *backupProgress))progress + failure:(nullable void (^)(NSError *error))failure; + +/** + Get the current backup progress. + + Can be called at any `MXKeyBackup` state. + `backupProgress.totalUnitCount` represents the total number of (group sessions) keys. + `backupProgress.completedUnitCount` is the number of keys already backed up. + + @param backupProgress the current backup progress + */ +- (void)backupProgress:(void (^)(NSProgress *backupProgress))backupProgress; + + +#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 from a given backup version stored on the homeserver. + + @param version the backup version to restore from. + @param recoveryKey the recovery key to decrypt the retrieved backup. + @param roomId the id of the room to get backup data from. + @param sessionId the id of the session to restore. + + @param success A block object called when the operation succeeds. + It provides the number of found keys and the number of successfully imported keys. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)restoreKeyBackup:(NSString*)version + recoveryKey:(NSString*)recoveryKey + room:(nullable NSString*)roomId + session:(nullable NSString*)sessionId + success:(nullable void (^)(NSUInteger total, NSUInteger imported))success + failure:(nullable void (^)(NSError *error))failure; + +#pragma mark - Backup state + +/** + The backup state. + */ +@property (nonatomic, readonly) MXKeyBackupState state; + +/** + Indicate if the backup is enabled. + */ +@property (nonatomic, readonly) BOOL enabled; + +/** + The backup version being used. + */ +@property (nonatomic, readonly, nullable) MXKeyBackupVersion *keyBackupVersion; + +/** + The backup key being used. + */ +@property (nonatomic, readonly, nullable) OLMPkEncryption *backupKey; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m new file mode 100644 index 0000000000..6794c5557c --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup.m @@ -0,0 +1,948 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXKeyBackup.h" +#import "MXKeyBackup_Private.h" + +#import "MXCrypto_Private.h" + +#import +#import "MXRecoveryKey.h" +#import "MXSession.h" +#import "MXTools.h" +#import "MXError.h" + + +#pragma mark - Constants definitions + +NSString *const kMXKeyBackupDidStateChangeNotification = @"kMXKeyBackupDidStateChangeNotification"; + +/** + Maximum delay in ms in `[MXKeyBackup maybeSendKeyBackup]`. + */ +NSUInteger const kMXKeyBackupWaitingTimeToSendKeyBackup = 10000; + +/** + Maximum number of keys to send at a time to the homeserver. + */ +NSUInteger const kMXKeyBackupSendKeysMaxCount = 100; + + +@interface MXKeyBackup () +{ + MXSession *mxSession; + + // Observer to kMXKeyBackupDidStateChangeNotification when backupAllGroupSessions is progressing + id backupAllGroupSessionsObserver; + + // Failure block when backupAllGroupSessions is progressing + void (^backupAllGroupSessionsFailure)(NSError *error); +} + +@end + +@implementation MXKeyBackup + +#pragma mark - SDK-Private methods - + +- (instancetype)initWithMatrixSession:(MXSession *)matrixSession +{ + self = [self init]; + { + _state = MXKeyBackupStateUnknown; + mxSession = matrixSession; + } + return self; +} + +- (void)checkAndStartKeyBackup +{ + self.state = MXKeyBackupStateCheckingBackUpOnHomeserver; + + MXWeakify(self); + [self version:^(MXKeyBackupVersion * _Nullable keyBackupVersion) { + MXStrongifyAndReturnIfNil(self); + + MXWeakify(self); + dispatch_async(self->mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + if (!keyBackupVersion) + { + NSLog(@"[MXKeyBackup] checkAndStartKeyBackup: Found no key backup version on the homeserver"); + [self disableKeyBackup]; + return; + } + + MXWeakify(self); + [self isKeyBackupTrusted:keyBackupVersion onComplete:^(MXKeyBackupVersionTrust * _Nonnull trustInfo) { + MXStrongifyAndReturnIfNil(self); + + self.state = MXKeyBackupStateDisabled; + + if (trustInfo.usable) + { + NSLog(@"[MXKeyBackup] checkAndStartKeyBackup: Found usable key backup. version: %@", keyBackupVersion.version); + if (!self.keyBackupVersion) + { + // Check the version we used at the previous app run + NSString *versionInStore = self->mxSession.crypto.store.backupVersion; + if (versionInStore && ![versionInStore isEqualToString:keyBackupVersion.version]) + { + NSLog(@"[MXKeyBackup] -> clean the previously used version(%@)", versionInStore); + [self disableKeyBackup]; + } + + NSLog(@"[MXKeyBackup] -> enabling key backups"); + [self enableKeyBackup:keyBackupVersion]; + } + else if ([self.keyBackupVersion.version isEqualToString:keyBackupVersion.version]) + { + NSLog(@"[MXKeyBackup] -> same backup version(%@). Keep using it", self.keyBackupVersion.version); + } + else + { + NSLog(@"[MXKeyBackup] -> disable the current version(%@) and enabling the new one", self.keyBackupVersion.version); + [self disableKeyBackup]; + [self enableKeyBackup:keyBackupVersion]; + } + } + else + { + NSLog(@"[MXKeyBackup] checkAndStartKeyBackup: No usable key backup. version: %@", keyBackupVersion.version); + if (!self.keyBackupVersion) + { + NSLog(@"[MXKeyBackup] -> not enabling key backup"); + } + else + { + NSLog(@"[MXKeyBackup] -> disabling key backup"); + [self disableKeyBackup]; + } + } + }]; + }); + + } failure:^(NSError * _Nonnull error) { + MXStrongifyAndReturnIfNil(self); + + MXWeakify(self); + dispatch_async(self->mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + NSLog(@"[MXKeyBackup] checkAndStartKeyBackup: Failed to get current version: %@", error); + self.state = MXKeyBackupStateUnknown; + }); + }]; +} + +/** + Enable backing up of keys. + + @param keyBackupVersion backup information object as returned by `[MXKeyBackup version]`. + @return an error if the operation fails. + */ +- (NSError*)enableKeyBackup:(MXKeyBackupVersion*)version +{ + MXMegolmBackupAuthData *authData = [MXMegolmBackupAuthData modelFromJSON:version.authData]; + if (authData) + { + _keyBackupVersion = version; + self->mxSession.crypto.store.backupVersion = version.version; + _backupKey = [OLMPkEncryption new]; + [_backupKey setRecipientKey:authData.publicKey]; + + self.state = MXKeyBackupStateReadyToBackUp; + + [self maybeSendKeyBackup]; + + return nil; + } + + return [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorInvalidParametersCode + userInfo:@{ + NSLocalizedDescriptionKey: @"Invalid authentication data", + }]; +} + +- (void)disableKeyBackup +{ + [self resetBackupAllGroupSessionsObjects]; + + _keyBackupVersion = nil; + self->mxSession.crypto.store.backupVersion = nil; + _backupKey = nil; + self.state = MXKeyBackupStateDisabled; + + // Reset backup markers + [self->mxSession.crypto.store resetBackupMarkers]; +} + +- (void)maybeSendKeyBackup +{ + if (_state == MXKeyBackupStateReadyToBackUp) + { + self.state = MXKeyBackupStateWillBackUp; + + // Wait between 0 and 10 seconds, to avoid backup requests from + // different clients hitting the server all at the same time when a + // new key is sent + NSUInteger delayInMs = arc4random_uniform(kMXKeyBackupWaitingTimeToSendKeyBackup); + + MXWeakify(self); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInMs * NSEC_PER_MSEC)), mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + [self sendKeyBackup]; + }); + } + else + { + NSLog(@"[MXKeyBackup] maybeSendKeyBackup: Skip it because state: %@", @(_state)); + + if (self.state == MXKeyBackupStateUnknown) + { + // If not already done, check for a valid backup version on the homeserver. + // If one, maybeSendKeyBackup will be called again. + [self checkAndStartKeyBackup]; + } + } +} + +- (void)sendKeyBackup +{ + NSLog(@"[MXKeyBackup] sendKeyBackup"); + + // Get a chunk of keys to backup + NSArray *sessions = [mxSession.crypto.store inboundGroupSessionsToBackup:kMXKeyBackupSendKeysMaxCount]; + + NSLog(@"[MXKeyBackup] sendKeyBackup: 1 - %@ sessions to back up", @(sessions.count)); + + if (!sessions.count) + { + // Backup is up to date + self.state = MXKeyBackupStateReadyToBackUp; + return; + } + + if (_state == MXKeyBackupStateBackingUp) + { + // Do nothing if we are already backing up + return; + } + + // Sanity check + if (!self.enabled || !_backupKey || !_keyBackupVersion) + { + NSLog(@"[MXKeyBackup] sendKeyBackup: Invalid state: %@", @(_state)); + if (backupAllGroupSessionsFailure) + { + NSError *error = [NSError errorWithDomain:MXKeyBackupErrorDomain + code:MXKeyBackupErrorInvalidStateCode + userInfo:@{ + NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Invalid state (%@) for making a backup", @(_state)] + }]; + backupAllGroupSessionsFailure(error); + } + return; + } + + self.state = MXKeyBackupStateBackingUp; + + NSLog(@"[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 = [self encryptGroupSession:session]; + + if (!roomsKeyBackup[session.roomId]) + { + roomsKeyBackup[session.roomId] = [NSMutableDictionary dictionary]; + } + roomsKeyBackup[session.roomId][session.session.sessionIdentifier] = keyBackupData; + } + + NSLog(@"[MXKeyBackup] sendKeyBackup: 3 - 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; + + NSLog(@"[MXKeyBackup] sendKeyBackup: 4 - Sending request"); + + // Make the request + MXWeakify(self); + [mxSession.crypto.matrixRestClient sendKeysBackup:keysBackupData version:_keyBackupVersion.version success:^{ + MXStrongifyAndReturnIfNil(self); + + NSLog(@"[MXKeyBackup] sendKeyBackup: 5a - Request complete"); + + // Mark keys as backed up + for (MXOlmInboundGroupSession *session in sessions) + { + [self->mxSession.crypto.store markBackupDoneForInboundGroupSessionWithId:session.session.sessionIdentifier andSenderKey:session.senderKey]; + } + + if (sessions.count < kMXKeyBackupSendKeysMaxCount) + { + NSLog(@"[MXKeyBackup] sendKeyBackup: All keys have been backed up"); + self.state = MXKeyBackupStateReadyToBackUp; + } + else + { + NSLog(@"[MXKeyBackup] sendKeyBackup: Continue to back up keys"); + self.state = MXKeyBackupStateWillBackUp; + + [self sendKeyBackup]; + } + + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + NSLog(@"[MXKeyBackup] sendKeyBackup: 5b - sendKeysBackup failed. Error: %@", error); + + void (^backupAllGroupSessionsFailure)(NSError *error) = self->backupAllGroupSessionsFailure; + + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if ([mxError.errcode isEqualToString:kMXErrCodeStringBackupWrongKeysVersion]) + { + [self disableKeyBackup]; + self.state = MXKeyBackupStateWrongBackUpVersion; + } + else + { + // Retry a bit later + self.state = MXKeyBackupStateReadyToBackUp; + [self maybeSendKeyBackup]; + } + + if (backupAllGroupSessionsFailure) + { + backupAllGroupSessionsFailure(error); + } + }]; +} + + +#pragma mark - Public methods - + +#pragma mark - Backup management + +- (MXHTTPOperation *)version:(void (^)(MXKeyBackupVersion * _Nullable))success failure:(void (^)(NSError * _Nonnull))failure +{ + // Use mxSession.matrixRestClient to respond to the main thread as this method is public + return [mxSession.matrixRestClient keyBackupVersion:success failure:^(NSError *error) { + + // Workaround because the homeserver currently returns M_NOT_FOUND when there is + // no key backup + MXError *mxError = [[MXError alloc] initWithNSError:error]; + if ([mxError.errcode isEqualToString:kMXErrCodeStringNotFound]) + { + if (success) + { + success(nil); + } + } + else if (failure) + { + failure(error); + } + }]; +} + +- (void)isKeyBackupTrusted:(MXKeyBackupVersion *)keyBackupVersion onComplete:(void (^)(MXKeyBackupVersionTrust * _Nonnull))onComplete +{ + MXWeakify(self); + dispatch_async(mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + NSString *myUserId = self->mxSession.myUser.userId; + + MXKeyBackupVersionTrust *keyBackupVersionTrust = [MXKeyBackupVersionTrust new]; + + MXMegolmBackupAuthData *authData = [MXMegolmBackupAuthData modelFromJSON:keyBackupVersion.authData]; + if (!keyBackupVersion.algorithm || !authData + || !authData.publicKey || !authData.signatures) + { + NSLog(@"[MXKeyBackup] isKeyBackupTrusted: Key backup is absent or missing required data"); + dispatch_async(dispatch_get_main_queue(), ^{ + onComplete(keyBackupVersionTrust); + }); + return; + } + + NSDictionary *mySigs = authData.signatures[myUserId]; + if (mySigs.count == 0) + { + NSLog(@"[MXKeyBackup] isKeyBackupTrusted: Ignoring key backup because it lacks any signatures from this user"); + dispatch_async(dispatch_get_main_queue(), ^{ + onComplete(keyBackupVersionTrust); + }); + return; + } + + 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]; + } + + MXDeviceInfo *device; + if (deviceId) + { + device = [self->mxSession.crypto.deviceList storedDevice:myUserId deviceId:deviceId]; + } + if (!device) + { + NSLog(@"[MXKeyBackup] isKeyBackupTrusted: Ignoring signature from unknown key %@", deviceId); + continue; + } + + NSError *error; + BOOL valid = [self->mxSession.crypto.olmDevice verifySignature:device.fingerprint JSON:authData.signalableJSONDictionary signature:mySigs[keyId] error:&error]; + + if (!valid) + { + NSLog(@"[MXKeyBackup] isKeyBackupTrusted: Bad signature from device %@: %@", device.deviceId, error); + } + else if (device.verified) + { + keyBackupVersionTrust.usable = YES; + } + + MXKeyBackupVersionTrustSignature *signature = [MXKeyBackupVersionTrustSignature new]; + signature.device = device; + signature.valid = valid; + + [signatures addObject:signature]; + } + + keyBackupVersionTrust.signatures = signatures; + + dispatch_async(dispatch_get_main_queue(), ^{ + onComplete(keyBackupVersionTrust); + }); + }); +} + +- (void)prepareKeyBackupVersion:(void (^)(MXMegolmBackupCreationInfo *keyBackupCreationInfo))success + failure:(nullable void (^)(NSError *error))failure; +{ + MXWeakify(self); + dispatch_async(mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + OLMPkDecryption *decryption = [OLMPkDecryption new]; + + NSError *error; + MXMegolmBackupAuthData *authData = [MXMegolmBackupAuthData new]; + authData.publicKey = [decryption generateKey:&error]; + if (error) + { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + return; + } + authData.signatures = [self->mxSession.crypto signObject:authData.signalableJSONDictionary]; + + MXMegolmBackupCreationInfo *keyBackupCreationInfo = [MXMegolmBackupCreationInfo new]; + keyBackupCreationInfo.algorithm = kMXCryptoMegolmBackupAlgorithm; + keyBackupCreationInfo.authData = authData; + keyBackupCreationInfo.recoveryKey = [MXRecoveryKey encode:decryption.privateKey]; + + dispatch_async(dispatch_get_main_queue(), ^{ + success(keyBackupCreationInfo); + }); + }); +} + +- (MXHTTPOperation*)createKeyBackupVersion:(MXMegolmBackupCreationInfo*)keyBackupCreationInfo + success:(void (^)(MXKeyBackupVersion *keyBackupVersion))success + failure:(nullable void (^)(NSError *error))failure +{ + MXHTTPOperation *operation = [MXHTTPOperation new]; + + [self setState:MXKeyBackupStateEnabling]; + + MXWeakify(self); + dispatch_async(mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + MXKeyBackupVersion *keyBackupVersion = [MXKeyBackupVersion new]; + keyBackupVersion.algorithm = keyBackupCreationInfo.algorithm; + keyBackupVersion.authData = keyBackupCreationInfo.authData.JSONDictionary; + + MXHTTPOperation *operation2 = [self->mxSession.crypto.matrixRestClient createKeyBackupVersion:keyBackupVersion success:^(NSString *version) { + + // Reset backup markers + [self->mxSession.crypto.store resetBackupMarkers]; + + keyBackupVersion.version = version; + + NSError *error = [self enableKeyBackup:keyBackupVersion]; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (!error) + { + success(keyBackupVersion); + } + else if (failure) + { + failure(error); + } + }); + + } failure:^(NSError *error) { + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + }); + + return operation; +} + +- (MXHTTPOperation*)deleteKeyBackupVersion:(NSString*)version + success:(void (^)(void))success + failure:(nullable void (^)(NSError *error))failure +{ + MXHTTPOperation *operation = [MXHTTPOperation new]; + + MXWeakify(self); + dispatch_async(mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + // If we're currently backing up to this backup... stop. + // (We start using it automatically in createKeyBackupVersion + // so this is symmetrical). + if ([self.keyBackupVersion.version isEqualToString:version]) + { + [self disableKeyBackup]; + self.state = MXKeyBackupStateUnknown; + } + + MXHTTPOperation *operation2 = [self->mxSession.crypto.matrixRestClient deleteKeysFromBackup:version success:^{ + + dispatch_async(dispatch_get_main_queue(), ^{ + success(); + }); + + } failure:^(NSError *error) { + if (failure) { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + }); + + return operation; +} + + +#pragma mark - Backup storing + +- (void)backupAllGroupSessions:(nullable void (^)(void))success + progress:(nullable void (^)(NSProgress *backupProgress))progress + failure:(nullable void (^)(NSError *error))failure; +{ + // Get a status right now + MXWeakify(self); + [self backupProgress:^(NSProgress * _Nonnull backupProgress) { + MXStrongifyAndReturnIfNil(self); + + // Reset previous state if any + [self resetBackupAllGroupSessionsObjects]; + + NSLog(@"[MXKeyBackup] backupAllGroupSessions: backupProgress: %@", backupProgress); + + if (progress) + { + progress(backupProgress); + } + + if (backupProgress.finished) + { + NSLog(@"[MXKeyBackup] backupAllGroupSessions: complete"); + if (success) + { + success(); + } + return; + } + + // Listen to `self.state` change to determine when to call onBackupProgress and onComplete + MXWeakify(self); + self->backupAllGroupSessionsObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKeyBackupDidStateChangeNotification object:self queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + + [self backupProgress:^(NSProgress * _Nonnull backupProgress) { + + if (progress) + { + progress(backupProgress); + } + + if (self.state == MXKeyBackupStateReadyToBackUp) + { + [self resetBackupAllGroupSessionsObjects]; + + if (success) + { + success(); + } + } + }]; + }]; + + dispatch_async(self->mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + // Listen to error + if (failure) + { + MXWeakify(self); + self->backupAllGroupSessionsFailure = ^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + + [self resetBackupAllGroupSessionsObjects]; + }; + } + + [self sendKeyBackup]; + }); + }]; +} + +- (void)resetBackupAllGroupSessionsObjects +{ + if (backupAllGroupSessionsObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:backupAllGroupSessionsObserver]; + backupAllGroupSessionsObserver = nil; + } + backupAllGroupSessionsFailure = nil; +} + +- (void)backupProgress:(void (^)(NSProgress *backupProgress))backupProgress +{ + MXWeakify(self); + dispatch_async(mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + NSUInteger keys = [self->mxSession.crypto.store inboundGroupSessionsCount:NO]; + NSUInteger backedUpkeys = [self->mxSession.crypto.store inboundGroupSessionsCount:YES]; + + NSProgress *progress = [NSProgress progressWithTotalUnitCount:keys]; + progress.completedUnitCount = backedUpkeys; + + dispatch_async(dispatch_get_main_queue(), ^{ + backupProgress(progress); + }); + }); +} + + +#pragma mark - Backup restoring + ++ (BOOL)isValidRecoveryKey:(NSString*)recoveryKey +{ + NSError *error; + NSData *privateKeyOut = [MXRecoveryKey decode:recoveryKey error:&error]; + + return !error && privateKeyOut; +} + +- (MXHTTPOperation*)restoreKeyBackup:(NSString*)version + recoveryKey:(NSString*)recoveryKey + room:(nullable NSString*)roomId + session:(nullable NSString*)sessionId + success:(nullable void (^)(NSUInteger total, NSUInteger imported))success + failure:(nullable void (^)(NSError *error))failure +{ + MXHTTPOperation *operation = [MXHTTPOperation new]; + + NSLog(@"[MXKeyBackup] restoreKeyBackup: From backup version: %@", version); + + MXWeakify(self); + dispatch_async(mxSession.crypto.cryptoQueue, ^{ + MXStrongifyAndReturnIfNil(self); + + // Get a PK decryption instance + NSError *error; + OLMPkDecryption *decryption = [self pkDecryptionFromRecoveryKey:recoveryKey error:&error]; + if (error) + { + NSLog(@"[MXKeyBackup] restoreKeyBackup: Invalid recovery key. Error: %@", error); + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + return; + } + + // Get backup from the homeserver + MXWeakify(self); + MXHTTPOperation *operation2 = [self keyBackupForSession:sessionId inRoom:roomId version:version success:^(MXKeysBackupData *keysBackupData) { + MXStrongifyAndReturnIfNil(self); + + NSMutableArray *sessionDatas = [NSMutableArray array]; + + // Restore that data + for (NSString *roomId in keysBackupData.rooms) + { + for (NSString *sessionId in keysBackupData.rooms[roomId].sessions) + { + MXKeyBackupData *keyBackupData = keysBackupData.rooms[roomId].sessions[sessionId]; + + MXMegolmSessionData *sessionData = [self decryptKeyBackupData:keyBackupData forSession:sessionId inRoom:roomId withPkDecryption:decryption]; + + [sessionDatas addObject:sessionData]; + } + } + + NSLog(@"[MXKeyBackup] restoreKeyBackup: Got %@ keys from the backup store on the homeserver", @(sessionDatas.count)); + + // Do not trigger a backup for them if they come from the backup version we are using + BOOL backUp = ![version isEqualToString:self.keyBackupVersion.version]; + if (backUp) + { + NSLog(@"[MXKeyBackup] restoreKeyBackup: Those keys will be backed up to backup version: %@", self.keyBackupVersion.version); + } + + // Import them into the crypto store + [self->mxSession.crypto importMegolmSessionDatas:sessionDatas backUp:backUp success:success failure:^(NSError *error) { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; + + } failure:^(NSError *error) { + if (failure) + { + dispatch_async(dispatch_get_main_queue(), ^{ + failure(error); + }); + } + }]; + + if (operation2) + { + [operation mutateTo:operation2]; + } + }); + + return operation; +} + + +#pragma mark - Backup state + +- (BOOL)enabled +{ + return _state >= MXKeyBackupStateReadyToBackUp; +} + + +#pragma mark - Private methods - + +- (void)setState:(MXKeyBackupState)state +{ + NSLog(@"[MXKeyBackup] setState: %@ -> %@", @(_state), @(state)); + + _state = state; + + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kMXKeyBackupDidStateChangeNotification object:self]; + }); +} + +// Same method as [MXRestClient keysBackupInRoom] except that it accepts nullable +// parameters and always returns a MXKeysBackupData object +- (MXHTTPOperation*)keyBackupForSession:(nullable NSString*)sessionId + inRoom:(nullable NSString*)roomId + version:(NSString*)version + success:(void (^)(MXKeysBackupData *keysBackupData))success + failure:(void (^)(NSError *error))failure; +{ + MXHTTPOperation *operation; + + if (!sessionId && !roomId) + { + operation = [mxSession.crypto.matrixRestClient keysBackup:version success:success failure:failure]; + } + else if (!sessionId) + { + operation = [mxSession.crypto.matrixRestClient keysBackupInRoom:roomId version:version success:^(MXRoomKeysBackupData *roomKeysBackupData) { + + MXKeysBackupData *keysBackupData = [MXKeysBackupData new]; + keysBackupData.rooms = @{ + roomId: roomKeysBackupData + }; + + success(keysBackupData); + + } failure:failure]; + } + else + { + operation = [mxSession.crypto.matrixRestClient keyBackupForSession:sessionId inRoom:roomId version:version success:^(MXKeyBackupData *keyBackupData) { + + MXRoomKeysBackupData *roomKeysBackupData = [MXRoomKeysBackupData new]; + roomKeysBackupData.sessions = @{ + sessionId: keyBackupData + }; + + MXKeysBackupData *keysBackupData = [MXKeysBackupData new]; + keysBackupData.rooms = @{ + roomId: roomKeysBackupData + }; + + success(keysBackupData); + + } failure:failure]; + } + + return operation; +} + +- (OLMPkDecryption*)pkDecryptionFromRecoveryKey:(NSString*)recoveryKey error:(NSError **)error +{ + // Extract the primary 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; +} + +- (MXKeyBackupData*)encryptGroupSession:(MXOlmInboundGroupSession*)session +{ + // Gather information for each key + MXDeviceInfo *device = [mxSession.crypto.deviceList deviceWithIdentityKey:session.senderKey andAlgorithm:kMXCryptoMegolmAlgorithm]; + + // Build the m.megolm_backup.v1.curve25519-aes-sha2 data as defined at + // https://github.com/uhoreg/matrix-doc/blob/e2e_backup/proposals/1219-storing-megolm-keys-serverside.md#mmegolm_backupv1curve25519-aes-sha2-key-format + MXMegolmSessionData *sessionData = session.exportSessionData; + NSDictionary *sessionBackupData = @{ + @"algorithm": sessionData.algorithm, + @"sender_key": sessionData.senderKey, + @"sender_claimed_keys": sessionData.senderClaimedKeys, + @"forwarding_curve25519_key_chain": sessionData.forwardingCurve25519KeyChain ? sessionData.forwardingCurve25519KeyChain : @[], + @"session_key": sessionData.sessionKey + }; + OLMPkMessage *encryptedSessionBackupData = [_backupKey encryptMessage:[MXTools serialiseJSONObject:sessionBackupData] error:nil]; + + // Build backup data for that key + MXKeyBackupData *keyBackupData = [MXKeyBackupData new]; + keyBackupData.firstMessageIndex = session.session.firstKnownIndex; + keyBackupData.forwardedCount = session.forwardingCurve25519KeyChain.count; + keyBackupData.verified = device.verified; + keyBackupData.sessionData = @{ + @"ciphertext": encryptedSessionBackupData.ciphertext, + @"mac": encryptedSessionBackupData.mac, + @"ephemeral": encryptedSessionBackupData.ephemeralKey, + }; + + return keyBackupData; +} + +- (MXMegolmSessionData*)decryptKeyBackupData:(MXKeyBackupData*)keyBackupData forSession:(NSString*)sessionId inRoom:(NSString*)roomId withPkDecryption:(OLMPkDecryption*)decryption +{ + MXMegolmSessionData *sessionData; + + NSString *ciphertext, *mac, *ephemeralKey; + + MXJSONModelSetString(ciphertext, keyBackupData.sessionData[@"ciphertext"]); + MXJSONModelSetString(mac, keyBackupData.sessionData[@"mac"]); + MXJSONModelSetString(ephemeralKey, keyBackupData.sessionData[@"ephemeral"]); + + if (ciphertext && mac && ephemeralKey) + { + OLMPkMessage *encrypted = [[OLMPkMessage alloc] initWithCiphertext:ciphertext mac:mac ephemeralKey:ephemeralKey]; + + NSError *error; + NSDictionary *sessionBackupData = [MXTools deserialiseJSONString:[decryption decryptMessage:encrypted error:&error]]; + + if (sessionBackupData) + { + MXJSONModelSetMXJSONModel(sessionData, MXMegolmSessionData, sessionBackupData); + + sessionData.sessionId = sessionId; + sessionData.roomId = roomId; + } + } + + return sessionData; +} + +@end diff --git a/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h new file mode 100644 index 0000000000..1bba553e3d --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/MXKeyBackup_Private.h @@ -0,0 +1,51 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXKeyBackup.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + The `MXKeyBackup_Private` extension exposes internal operations. + */ +@interface MXKeyBackup () + +/** + + */ +- (instancetype)initWithMatrixSession:(MXSession*)mxSession; + +/** + 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; + +/** + * Disable backing up of keys. + */ +- (void)disableKeyBackup; + +/** + Do a backup if there are new keys. + */ +- (void)maybeSendKeyBackup; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h new file mode 100644 index 0000000000..91c75cb53f --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.h @@ -0,0 +1,63 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + + +/** + The error domain for this class. + */ +FOUNDATION_EXPORT NSString *const MXRecoveryKeyErrorDomain; + +/** + All associated error code. + */ +typedef enum : NSUInteger +{ + MXRecoveryKeyErrorBase58Code, + MXRecoveryKeyErrorParityCode, + MXRecoveryKeyErrorHeaderCode, + MXRecoveryKeyErrorLengthCode +} MXRecoveryKeyErrorCode; + + +/** + The `MXRecoveryKey` class encodes and decodes a recovery key used for backup. + */ +@interface MXRecoveryKey : NSObject + +/** + Encode a key into a base58 string. + + @param key the private key. + @return the base58 recovery key. + */ ++ (NSString *)encode:(NSData*)key; + +/** + Decode a base58 recovery key. + + @param key the recovery key. + @param error the output error. + @return the private key. + */ ++ (nullable NSData *)decode:(NSString*)recoveryKey error:(NSError * _Nullable *)error; + +@end + +NS_ASSUME_NONNULL_END diff --git a/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m new file mode 100644 index 0000000000..468d3ad1e6 --- /dev/null +++ b/MatrixSDK/Crypto/KeyBackup/MXRecoveryKey.m @@ -0,0 +1,178 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "MXRecoveryKey.h" + +#import "MXTools.h" + +#import + +#import + + +NSString *const MXRecoveryKeyErrorDomain = @"org.matrix.sdk.recoverykey"; + +// Picked arbitrarily bits to try & avoid clashing with any bitcoin ones +// (also base58 encoded, albeit with a lot of hashing) +const UInt8 kOlmRecoveryKeyPrefix[] = {0x8B, 0x01}; + +@implementation MXRecoveryKey + ++ (NSString *)encode:(NSData *)key +{ + // Prepend the recovery key 2-bytes header + NSMutableData *buffer = [NSMutableData dataWithBytes:kOlmRecoveryKeyPrefix length:sizeof(kOlmRecoveryKeyPrefix)]; + [buffer appendData:key]; + + // Add a parity checksum + UInt8 parity = 0; + UInt8 *bytes = (UInt8 *)buffer.bytes; + for (NSUInteger i = 0; i < buffer.length; i++) + { + parity ^= bytes[i]; + } + [buffer appendBytes:&parity length:sizeof(parity)]; + + // Encode it in Base58 + NSString *recoveryKey = [self encodeBase58:buffer]; + + // Add white spaces + return [MXTools addWhiteSpacesToString:recoveryKey every:4]; +} + ++ (NSData *)decode:(NSString *)recoveryKey error:(NSError **)error +{ + NSString *recoveryKeyWithNoSpaces = [recoveryKey stringByReplacingOccurrencesOfString:@" " withString:@""]; + NSMutableData *result = [[self decodeBase58:recoveryKeyWithNoSpaces] mutableCopy]; + + if (!result) + { + *error = [NSError errorWithDomain:MXRecoveryKeyErrorDomain + code:MXRecoveryKeyErrorBase58Code + userInfo:@{ + NSLocalizedDescriptionKey: @"Cannot decode Base58 string", + }]; + return nil; + } + + // Check length + if (result.length != + sizeof(kOlmRecoveryKeyPrefix) + [OLMPkDecryption privateKeyLength] + 1) + { + if (error) + { + *error = [NSError errorWithDomain:MXRecoveryKeyErrorDomain + code:MXRecoveryKeyErrorLengthCode + userInfo:@{ + NSLocalizedDescriptionKey: @"Incorrect length", + }]; + } + return nil; + } + + // Check the checksum + UInt8 parity = 0; + UInt8 *bytes = (UInt8 *)result.bytes; + for (NSUInteger i = 0; i < result.length; i++) + { + parity ^= bytes[i]; + } + if (parity != 0) + { + if (error) + { + *error = [NSError errorWithDomain:MXRecoveryKeyErrorDomain + code:MXRecoveryKeyErrorParityCode + userInfo:@{ + NSLocalizedDescriptionKey: @"Incorrect parity", + }]; + } + return nil; + } + + // Check recovery key header + for (NSUInteger i = 0; i < sizeof(kOlmRecoveryKeyPrefix); i++) + { + if (bytes[i] != kOlmRecoveryKeyPrefix[i]) + { + if (error) + { + *error = [NSError errorWithDomain:MXRecoveryKeyErrorDomain + code:MXRecoveryKeyErrorHeaderCode + userInfo:@{ + NSLocalizedDescriptionKey: @"Invalid header", + }]; + } + return nil; + } + } + + // Remove header and checksum bytes + [result replaceBytesInRange:NSMakeRange(0, sizeof(kOlmRecoveryKeyPrefix)) withBytes:NULL length:0]; + result.length -= 1; + + return result; +} + + +#pragma mark - Private methods + ++ (NSString *)encodeBase58:(NSData *)data +{ + NSString *base58; + + // Get the required buffer size + size_t base58Length = 0; + b58enc(nil, &base58Length, data.bytes, data.length); + + // Encode + NSMutableData *base58Data = [NSMutableData dataWithLength:base58Length]; + BOOL result = b58enc(base58Data.mutableBytes, &base58Length, data.bytes, data.length); + + if (result) + { + base58 = [[NSString alloc] initWithData:base58Data encoding:NSUTF8StringEncoding]; + base58 = [base58 substringToIndex:base58Length - 1]; + } + + return base58; +} + ++ (NSData *)decodeBase58:(NSString *)base58 +{ + NSMutableData *data; + + NSData *base58Data = [base58 dataUsingEncoding:NSUTF8StringEncoding]; + + // Get the required buffer size + // We need to pass a non null buffer, so allocate one using the base64 string length + // The decoded buffer can only be smaller + size_t dataLength = base58.length; + data = [NSMutableData dataWithLength:dataLength]; + b58tobin(data.mutableBytes, &dataLength, base58Data.bytes, base58Data.length); + + // Decode with the actual result size + data = [NSMutableData dataWithLength:dataLength]; + BOOL result = b58tobin(data.mutableBytes, &dataLength, base58Data.bytes, base58Data.length); + if (!result) + { + data = nil; + } + + return data; +} + +@end diff --git a/MatrixSDK/Crypto/MXCrypto.h b/MatrixSDK/Crypto/MXCrypto.h index 716282c9c0..9d01aa9755 100644 --- a/MatrixSDK/Crypto/MXCrypto.h +++ b/MatrixSDK/Crypto/MXCrypto.h @@ -27,6 +27,8 @@ #import "MXIncomingRoomKeyRequest.h" #import "MXIncomingRoomKeyRequestCancellation.h" +#import "MXKeyBackup.h" + @class MXSession; /** @@ -75,6 +77,11 @@ FOUNDATION_EXPORT NSString *const kMXCryptoRoomKeyRequestCancellationNotificatio */ @property (nonatomic, readonly) NSString *olmVersion; +/** + The key backup manager. + */ +@property (nonatomic, readonly) MXKeyBackup *backup; + /** Create a new crypto instance and data for the given user. @@ -290,10 +297,11 @@ FOUNDATION_EXPORT NSString *const kMXCryptoRoomKeyRequestCancellationNotificatio Import a list of room keys previously exported by exportRoomKeys. @param success A block object called when the operation succeeds. + It provides the number of found keys and the number of successfully imported keys. @param failure A block object called when the operation fails. */ - (void)importRoomKeys:(NSArray*)keys - success:(void (^)(void))success + success:(void (^)(NSUInteger total, NSUInteger imported))success failure:(void (^)(NSError *error))failure; /** @@ -302,10 +310,11 @@ FOUNDATION_EXPORT NSString *const kMXCryptoRoomKeyRequestCancellationNotificatio @param keyFile the encrypted keys file data. @password the passphrase used to decrypts keys. @param success A block object called when the operation succeeds. + It provides the number of found keys and the number of successfully imported keys. @param failure A block object called when the operation fails. */ - (void)importRoomKeys:(NSData *)keyFile withPassword:(NSString*)password - success:(void (^)(void))success + success:(void (^)(NSUInteger total, NSUInteger imported))success failure:(void (^)(NSError *error))failure; diff --git a/MatrixSDK/Crypto/MXCrypto.m b/MatrixSDK/Crypto/MXCrypto.m index 71744d807a..cf38c5779b 100644 --- a/MatrixSDK/Crypto/MXCrypto.m +++ b/MatrixSDK/Crypto/MXCrypto.m @@ -246,6 +246,8 @@ - (void)start:(void (^)(void))success [self->outgoingRoomKeyRequestManager start]; + [self->_backup checkAndStartKeyBackup]; + dispatch_async(dispatch_get_main_queue(), ^{ self->startOperation = nil; success(); @@ -713,7 +715,7 @@ - (MXDeviceInfo *)eventDeviceInfo:(MXEvent *)event MXStrongifyAndReturnIfNil(self); NSString *algorithm = event.wireContent[@"algorithm"]; - device = [self.deviceList deviceWithIdentityKey:event.senderKey forUser:event.sender andAlgorithm:algorithm]; + device = [self.deviceList deviceWithIdentityKey:event.senderKey andAlgorithm:algorithm]; }); } @@ -741,10 +743,13 @@ - (void)setDeviceVerification:(MXDeviceVerification)verificationStatus forDevice if (!device) { NSLog(@"[MXCrypto] setDeviceVerificationForDevice: Unknown device %@:%@", userId, deviceId); - - dispatch_async(dispatch_get_main_queue(), ^{ - success(); - }); + + if (success) + { + dispatch_async(dispatch_get_main_queue(), ^{ + success(); + }); + } return; } @@ -752,6 +757,14 @@ - (void)setDeviceVerification:(MXDeviceVerification)verificationStatus forDevice { device.verified = verificationStatus; [self.store storeDeviceForUser:userId device:device]; + + if ([userId isEqualToString:self.mxSession.myUser.userId]) + { + // If one of the user's own devices is being marked as verified / unverified, + // check the key backup status, since whether or not we use this depends on + // whether it has a signature from a verified device + [self.backup checkAndStartKeyBackup]; + } } if (success) @@ -1001,18 +1014,32 @@ - (void)exportRoomKeysWithPassword:(NSString *)password success:(void (^)(NSData #endif } -- (void)importRoomKeys:(NSArray *)keys success:(void (^)(void))success failure:(void (^)(NSError *))failure +- (void)importRoomKeys:(NSArray *)keys success:(void (^)(NSUInteger total, NSUInteger imported))success failure:(void (^)(NSError *))failure { #ifdef MX_CRYPTO dispatch_async(_decryptionQueue, ^{ NSLog(@"[MXCrypto] importRoomKeys:"); - NSDate *startDate = [NSDate date]; - // Convert JSON to MXMegolmSessionData NSArray *sessionDatas = [MXMegolmSessionData modelsFromJSON:keys]; + [self importMegolmSessionDatas:sessionDatas backUp:YES success:success failure:failure]; + }); +#endif +} + +- (void)importMegolmSessionDatas:(NSArray*)sessionDatas backUp:(BOOL)backUp success:(void (^)(NSUInteger total, NSUInteger imported))success failure:(void (^)(NSError *error))failure +{ +#ifdef MX_CRYPTO + dispatch_async(_decryptionQueue, ^{ + + NSLog(@"[MXCrypto] importMegolmSessionDatas: backUp: %@", @(backUp)); + + NSUInteger imported = 0; + NSUInteger totalKeyCount = sessionDatas.count; + NSDate *startDate = [NSDate date]; + for (MXMegolmSessionData *sessionData in sessionDatas) { NSLog(@" - importing %@|%@", sessionData.senderKey, sessionData.sessionId); @@ -1025,16 +1052,19 @@ - (void)importRoomKeys:(NSArray *)keys success:(void (^)(void))s // Import the session id alg = [self getRoomDecryptor:sessionData.roomId algorithm:sessionData.algorithm]; - [alg importRoomKey:sessionData]; + if ([alg importRoomKey:sessionData backUp:backUp]) + { + imported++; + } } - NSLog(@"[MXCrypto] importRoomKeys: Imported %tu keys in %.0fms", keys.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000); + NSLog(@"[MXCrypto] importMegolmSessionDatas: Imported %tu keys from %tu provided keys in %.0fms", imported, totalKeyCount, [[NSDate date] timeIntervalSinceDate:startDate] * 1000); dispatch_async(dispatch_get_main_queue(), ^{ if (success) { - success(); + success(totalKeyCount, imported); } }); @@ -1042,7 +1072,7 @@ - (void)importRoomKeys:(NSArray *)keys success:(void (^)(void))s #endif } -- (void)importRoomKeys:(NSData *)keyFile withPassword:(NSString *)password success:(void (^)(void))success failure:(void (^)(NSError *))failure +- (void)importRoomKeys:(NSData *)keyFile withPassword:(NSString *)password success:(void (^)(NSUInteger total, NSUInteger imported))success failure:(void (^)(NSError *))failure { #ifdef MX_CRYPTO dispatch_async(_decryptionQueue, ^{ @@ -1058,13 +1088,13 @@ - (void)importRoomKeys:(NSData *)keyFile withPassword:(NSString *)password succe NSArray *keys = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; if (keys) { - [self importRoomKeys:keys success:^{ + [self importRoomKeys:keys success:^(NSUInteger total, NSUInteger imported) { - NSLog(@"[MXCrypto] importRoomKeys:withPassord: Imported %tu keys in %.0fms", keys.count, [[NSDate date] timeIntervalSinceDate:startDate] * 1000); + NSLog(@"[MXCrypto] importRoomKeys:withPassord: Imported %tu keys from %tu provided keys in %.0fms", imported, total, [[NSDate date] timeIntervalSinceDate:startDate] * 1000); if (success) { - success(); + success(total, imported); } } failure:failure]; @@ -1080,9 +1110,7 @@ - (void)importRoomKeys:(NSData *)keyFile withPassword:(NSString *)password succe { failure(error); } - }); - }); #endif } @@ -1363,6 +1391,8 @@ - (instancetype)initWithMatrixSession:(MXSession*)matrixSession cryptoQueue:(dis oneTimeKeyCount = -1; + _backup = [[MXKeyBackup alloc] initWithMatrixSession:_mxSession]; + outgoingRoomKeyRequestManager = [[MXOutgoingRoomKeyRequestManager alloc] initWithMatrixRestClient:_matrixRestClient deviceId:myDevice.deviceId @@ -1398,7 +1428,7 @@ - (MXDeviceInfo *)eventSenderDeviceOfEvent:(MXEvent *)event // senderKey is the Curve25519 identity key of the device which the event // was sent from. In the case of Megolm, it's actually the Curve25519 // identity key of the device which set up the Megolm session. - MXDeviceInfo *device = [_deviceList deviceWithIdentityKey:senderKey forUser:event.sender andAlgorithm:algorithm]; + MXDeviceInfo *device = [_deviceList deviceWithIdentityKey:senderKey andAlgorithm:algorithm]; if (!device) { // we haven't downloaded the details of this device yet. @@ -1732,6 +1762,16 @@ - (NSDictionary*)encryptMessage:(NSDictionary*)payloadFields forDevices:(NSArray return alg; } +- (NSDictionary*)signObject:(NSDictionary*)object +{ + return @{ + myDevice.userId: @{ + [NSString stringWithFormat:@"ed25519:%@", myDevice.deviceId]: [_olmDevice signJSON:object] + } + }; +} + + #pragma mark - Key sharing - (void)requestRoomKey:(NSDictionary*)requestBody recipients:(NSArray*>*)recipients { @@ -2228,12 +2268,7 @@ - (MXHTTPOperation *)uploadOneTimeKeys:(void (^)(MXKeysUploadResponse *keysUploa // Sign each one-time key NSMutableDictionary *k = [NSMutableDictionary dictionary]; k[@"key"] = oneTimeKeys[@"curve25519"][keyId]; - - k[@"signatures"] = @{ - myDevice.userId: @{ - [NSString stringWithFormat:@"ed25519:%@", myDevice.deviceId]: [_olmDevice signJSON:k] - } - }; + k[@"signatures"] = [self signObject:k]; oneTimeJson[[NSString stringWithFormat:@"signed_curve25519:%@", keyId]] = k; } diff --git a/MatrixSDK/Crypto/MXCrypto_Private.h b/MatrixSDK/Crypto/MXCrypto_Private.h index c1f4118d2b..63effd1059 100644 --- a/MatrixSDK/Crypto/MXCrypto_Private.h +++ b/MatrixSDK/Crypto/MXCrypto_Private.h @@ -28,6 +28,7 @@ #import "MXCryptoAlgorithms.h" #import "MXUsersDevicesMap.h" #import "MXOlmSessionResult.h" +#import "MXKeyBackup_Private.h" #import "MXCrypto.h" @@ -150,6 +151,31 @@ */ - (id)getRoomDecryptor:(NSString*)roomId algorithm:(NSString*)algorithm; +/** + Sign the given object with our ed25519 key. + + @param Object the dictionary to sign. + @return signatures. + */ +- (NSDictionary*)signObject:(NSDictionary*)object; + + +#pragma mark - import/export + +/** + Import a list of megolm session keys. + + @param sessionDatas megolm sessions. + @param backUp YES to back up them to the homeserver. + @param success A block object called when the operation succeeds. + It provides the number of found keys and the number of successfully imported keys. + @param failure A block object called when the operation fails. + */ +- (void)importMegolmSessionDatas:(NSArray*)sessionDatas + backUp:(BOOL)backUp + success:(void (^)(NSUInteger total, NSUInteger imported))success + failure:(void (^)(NSError *error))failure; + #pragma mark - Key sharing diff --git a/MatrixSDK/Crypto/MXOlmDevice.h b/MatrixSDK/Crypto/MXOlmDevice.h index be74c39a65..f8203ad895 100644 --- a/MatrixSDK/Crypto/MXOlmDevice.h +++ b/MatrixSDK/Crypto/MXOlmDevice.h @@ -232,9 +232,10 @@ Determine if an incoming messages is a prekey message matching an existing sessi /** Add a previously-exported inbound group session to the session store. - @param data the session data + @param data the session data. + @return YES if the key has been imported. */ -- (void)importInboundGroupSession:(MXMegolmSessionData*)data; +- (BOOL)importInboundGroupSession:(MXMegolmSessionData*)data; /** Decrypt a received message with an inbound group session. diff --git a/MatrixSDK/Crypto/MXOlmDevice.m b/MatrixSDK/Crypto/MXOlmDevice.m index d0135c7d5e..d956182dc1 100644 --- a/MatrixSDK/Crypto/MXOlmDevice.m +++ b/MatrixSDK/Crypto/MXOlmDevice.m @@ -353,7 +353,7 @@ - (BOOL)addInboundGroupSession:(NSString*)sessionId sessionKey:(NSString*)sessio return YES; } -- (void)importInboundGroupSession:(MXMegolmSessionData *)data +- (BOOL)importInboundGroupSession:(MXMegolmSessionData *)data { NSError *error; MXOlmInboundGroupSession *session = [self inboundGroupSessionWithId:data.sessionId senderKey:data.senderKey roomId:data.roomId error:&error]; @@ -364,12 +364,14 @@ - (void)importInboundGroupSession:(MXMegolmSessionData *)data NSLog(@"[MXOlmDevice] importInboundGroupSession: Update for megolm session %@|%@", data.senderKey, data.sessionId); // For now we just ignore updates. TODO: implement something here - return; + return NO; } session = [[MXOlmInboundGroupSession alloc] initWithImportedSessionData:data]; [store storeInboundGroupSession:session]; + + return YES; } - (MXDecryptionResult *)decryptGroupMessage:(NSString *)body roomId:(NSString *)roomId diff --git a/MatrixSDK/JSONModels/MXJSONModels.h b/MatrixSDK/JSONModels/MXJSONModels.h index 23b0b5f050..7eb184143c 100644 --- a/MatrixSDK/JSONModels/MXJSONModels.h +++ b/MatrixSDK/JSONModels/MXJSONModels.h @@ -19,6 +19,9 @@ #import "MXJSONModel.h" #import "MXUsersDevicesMap.h" +#import "MXKeyBackupVersion.h" +#import "MXKeyBackupData.h" +#import "MXMegolmBackupAuthData.h" @class MXEvent, MXDeviceInfo, MXKey, MXUser; diff --git a/MatrixSDK/MXEnumConstants.h b/MatrixSDK/MXEnumConstants.h index db7acec104..dfe68fd3f4 100644 --- a/MatrixSDK/MXEnumConstants.h +++ b/MatrixSDK/MXEnumConstants.h @@ -33,6 +33,11 @@ FOUNDATION_EXPORT NSString *const kMXContentUriScheme; */ FOUNDATION_EXPORT NSString *const kMXContentPrefixPath; +/** + A constant representing the URI path for as-yet unspecified of the AntiVirus Client-Server HTTP API. + */ +FOUNDATION_EXPORT NSString *const kMXAntivirusAPIPrefixPathUnstable; + /** Methods of thumnailing supported by the Matrix content repository. */ diff --git a/MatrixSDK/MXEnumConstants.m b/MatrixSDK/MXEnumConstants.m index 5f760be52a..501dda965f 100644 --- a/MatrixSDK/MXEnumConstants.m +++ b/MatrixSDK/MXEnumConstants.m @@ -24,6 +24,11 @@ NSString *const kMXContentUriScheme = @"mxc://"; NSString *const kMXContentPrefixPath = @"_matrix/media/v1"; +/** + Prefix used in path of antivirus server API requests. + */ +NSString *const kMXAntivirusAPIPrefixPathUnstable = @"_matrix/media_proxy/unstable/"; + /** Membership definitions - String version */ diff --git a/MatrixSDK/MXError.h b/MatrixSDK/MXError.h index 2f2fb8563d..21ef7cabe7 100644 --- a/MatrixSDK/MXError.h +++ b/MatrixSDK/MXError.h @@ -44,6 +44,7 @@ FOUNDATION_EXPORT NSString *const kMXErrCodeStringServerNotTrusted; FOUNDATION_EXPORT NSString *const kMXErrCodeStringGuestAccessForbidden; FOUNDATION_EXPORT NSString *const kMXErrCodeStringConsentNotGiven; FOUNDATION_EXPORT NSString *const kMXErrCodeStringResourceLimitExceeded; +FOUNDATION_EXPORT NSString *const kMXErrCodeStringBackupWrongKeysVersion; FOUNDATION_EXPORT NSString *const kMXErrorStringInvalidToken; diff --git a/MatrixSDK/MXError.m b/MatrixSDK/MXError.m index aaafd05f61..1aa29e65e2 100644 --- a/MatrixSDK/MXError.m +++ b/MatrixSDK/MXError.m @@ -41,6 +41,7 @@ NSString *const kMXErrCodeStringGuestAccessForbidden = @"M_GUEST_ACCESS_FORBIDDEN"; NSString *const kMXErrCodeStringConsentNotGiven = @"M_CONSENT_NOT_GIVEN"; NSString *const kMXErrCodeStringResourceLimitExceeded = @"M_RESOURCE_LIMIT_EXCEEDED"; +NSString *const kMXErrCodeStringBackupWrongKeysVersion = @"M_WRONG_ROOM_KEYS_VERSION"; NSString *const kMXErrorStringInvalidToken = @"Invalid token"; diff --git a/MatrixSDK/MXRestClient.h b/MatrixSDK/MXRestClient.h index 51c35e622c..e4243d2491 100644 --- a/MatrixSDK/MXRestClient.h +++ b/MatrixSDK/MXRestClient.h @@ -33,6 +33,9 @@ #import "MXJSONModels.h" #import "MXFilterJSONModel.h" #import "MXMatrixVersions.h" +#import "MXContentScanResult.h" +#import "MXEncryptedContentFile.h" +#import "MXContentScanEncryptedBody.h" #pragma mark - Constants definitions /** @@ -41,7 +44,7 @@ FOUNDATION_EXPORT NSString *const kMXAPIPrefixPathR0; /** - A constant representing tthe URI path for as-yet unspecified of the Client-Server HTTP API. + A constant representing the URI path for as-yet unspecified of the Client-Server HTTP API. */ FOUNDATION_EXPORT NSString *const kMXAPIPrefixPathUnstable; @@ -87,7 +90,7 @@ FOUNDATION_EXPORT NSString *const kMXMembersOfRoomParametersNotMembership; @interface MXRestClient : NSObject /** - The homeserver. + The homeserver URL. */ @property (nonatomic, readonly) NSString *homeserver; @@ -114,12 +117,25 @@ FOUNDATION_EXPORT NSString *const kMXMembersOfRoomParametersNotMembership; @property (nonatomic) NSString *contentPathPrefix; /** - The identity server. + The identity server URL. By default, it points to the defined home server. If needed, change it by setting this property. */ @property (nonatomic) NSString *identityServer; +/** + The antivirus server URL (nil by default). + Set a non-null url to enable the antivirus scanner use. + */ +@property (nonatomic) NSString *antivirusServer; + +/** + The Client-Server API prefix to use for the antivirus server + By default, it is defined by the constant kMXAntivirusAPIPrefixPathUnstable. + In case of a custom path prefix use, set it before settings the antivirus server url. + */ +@property (nonatomic) NSString *antivirusServerPathPrefix; + /** The current trusted certificate (if any). */ @@ -1819,6 +1835,62 @@ FOUNDATION_EXPORT NSString *const kMXMembersOfRoomParametersNotMembership; success:(void (^)(NSDictionary *thirdPartySigned))success failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; + +#pragma mark - Antivirus server API + +/** + Get the current public curve25519 key that the antivirus server is advertising. + + @param success A block object called when the operation succeeds. It provides the antivirus public key. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)getAntivirusServerPublicKey:(void (^)(NSString *publicKey))success + failure:(void (^)(NSError *error))failure; + +/** + Scan an unencrypted content. + + @param mxcContentURI the Matrix content URI to scan (in the form of "mxc://..."). + @param success A block object called when the operation succeeds. It provides the scan result. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)scanUnencryptedContent:(NSString*)mxcContentURI + success:(void (^)(MXContentScanResult *scanResult))success + failure:(void (^)(NSError *error))failure; + +/** + Scan an encrypted content. + + @param encryptedContentFile the information of the encrypted content + @param success A block object called when the operation succeeds. It provides the scan result. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)scanEncryptedContent:(MXEncryptedContentFile*)encryptedContentFile + success:(void (^)(MXContentScanResult *scanResult))success + failure:(void (^)(NSError *error))failure; + +/** + Scan an encrypted content by sending an encrypted body (produced by considering the antivirus + server public key). + + @param encryptedbody the encrypted data used to + @param success A block object called when the operation succeeds. It provides the scan result. + @param failure A block object called when the operation fails. + + @return a MXHTTPOperation instance. + */ +- (MXHTTPOperation*)scanEncryptedContentWithSecureExchange:(MXContentScanEncryptedBody *)encryptedbody + success:(void (^)(MXContentScanResult *scanResult))success + failure:(void (^)(NSError *error))failure; + + +#pragma mark - Certificates /** Set the certificates used to evaluate server trust according to the SSL pinning mode. @@ -1826,6 +1898,7 @@ FOUNDATION_EXPORT NSString *const kMXMembersOfRoomParametersNotMembership; */ -(void)setPinnedCertificates:(NSSet *)pinnedCertificates; + #pragma mark - VoIP API /** Get the TURN server configuration advised by the homeserver. @@ -2000,6 +2073,186 @@ FOUNDATION_EXPORT NSString *const kMXMembersOfRoomParametersNotMembership; failure:(void (^)(NSError *error))failure; +#pragma mark - Crypto: e2e keys backup + +/** + Create a new backup version. + + @param keyBackupVersion backup information. + + @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*)createKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion + success:(void (^)(NSString *version))success + failure:(void (^)(NSError *error))failure; + +/** + Get information about the current version. + + @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*)keyBackupVersion:(void (^)(MXKeyBackupVersion *keyBackupVersion))success + failure:(void (^)(NSError *error))failure; + +/** + Back up a session key to the homeserver. + + @param keyBackupData the key to backup. + @param roomId the id of the room that the keys are for. + @param sessionId the id of the session that the keys are. + @param version the backup version. + + @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*)sendKeyBackup:(MXKeyBackupData*)keyBackupData + room:(NSString*)roomId + session:(NSString*)sessionId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure; + +/** + Back up keys in a room to the homeserver. + + @param roomKeysBackupData keys to backup. + @param roomId the id of the room that the keys are for. + @param version the backup version. + + @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*)sendRoomKeysBackup:(MXRoomKeysBackupData*)roomKeysBackupData + room:(NSString*)roomId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure; + +/** + Back up keys to the homeserver. + + @param keysBackupData keys to backup. + @param version the backup version. + + @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*)sendKeysBackup:(MXKeysBackupData*)keysBackupData + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure; + +/** + Retrieve the backup for a session key from the homeserver. + + @param sessionId the id of the session that the keys are. + @param roomId the id of the room that the keys are for. + @param version the backup version. + + @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*)keyBackupForSession:(NSString*)sessionId + inRoom:(NSString*)roomId + version:(NSString*)version + success:(void (^)(MXKeyBackupData *keyBackupData))success + failure:(void (^)(NSError *error))failure; + +/** + Retrieve the backup for all keys in a room from the homeserver. + + @param roomId the id of the room that the keys are for. + @param version the backup version. + + @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*)keysBackupInRoom:(NSString*)roomId + version:(NSString*)version + success:(void (^)(MXRoomKeysBackupData *roomKeysBackupData))success + failure:(void (^)(NSError *error))failure; + +/** + Retrieve all keys backup from the homeserver. + + @param version the backup version. + + @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*)keysBackup:(NSString*)version + success:(void (^)(MXKeysBackupData *keysBackupData))success + failure:(void (^)(NSError *error))failure; + +/** + Delete the backup for a session key from the homeserver. + + @param roomId the id of the room that the keys are for. + @param sessionId the id of the session that the keys are. + @param version the backup version. + + @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*)deleteKeyFromBackup:(NSString*)roomId + session:(NSString*)sessionId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure; + +/** + Delete the backup for all keys in a room from the homeserver. + + @param roomKeysBackupData keys to backup. + @param roomId the id of the room that the keys are for. + @param version the backup version. + + @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*)deleteKeysInRoomFromBackup:(NSString*)roomId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure; + +/** + Delete all keys backup from the homeserver. + + @param keysBackupData keys to backup. + @param version the backup version. + + @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*)deleteKeysFromBackup:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure; + + #pragma mark - Direct-to-device messaging /** Send an event to a specific list of devices @@ -2018,6 +2271,7 @@ FOUNDATION_EXPORT NSString *const kMXMembersOfRoomParametersNotMembership; success:(void (^)(void))success failure:(void (^)(NSError *error))failure NS_REFINED_FOR_SWIFT; + #pragma mark - Device Management /** Get information about all devices for the current user. diff --git a/MatrixSDK/MXRestClient.m b/MatrixSDK/MXRestClient.m index 205be28887..7fd0dad1c0 100644 --- a/MatrixSDK/MXRestClient.m +++ b/MatrixSDK/MXRestClient.m @@ -92,6 +92,11 @@ @interface MXRestClient () */ MXHTTPClient *identityHttpClient; + /** + HTTP client to the antivirus server. + */ + MXHTTPClient *antivirusHttpClient; + /** The queue to process server response. This queue is used to create models from JSON dictionary without blocking the main thread. @@ -101,7 +106,7 @@ @interface MXRestClient () @end @implementation MXRestClient -@synthesize homeserver, homeserverSuffix, credentials, apiPathPrefix, contentPathPrefix, completionQueue; +@synthesize homeserver, homeserverSuffix, credentials, apiPathPrefix, contentPathPrefix, completionQueue, antivirusServerPathPrefix; -(id)initWithHomeServer:(NSString *)inHomeserver andOnUnrecognizedCertificateBlock:(MXHTTPClientOnUnrecognizedCertificate)onUnrecognizedCertBlock { @@ -110,6 +115,7 @@ -(id)initWithHomeServer:(NSString *)inHomeserver andOnUnrecognizedCertificateBlo { homeserver = inHomeserver; apiPathPrefix = kMXAPIPrefixPathR0; + antivirusServerPathPrefix = kMXAntivirusAPIPrefixPathUnstable; contentPathPrefix = kMXContentPrefixPath; httpClient = [[MXHTTPClient alloc] initWithBaseURL:homeserver @@ -157,6 +163,7 @@ -(id)initWithCredentials:(MXCredentials*)inCredentials andOnUnrecognizedCertific { homeserver = inCredentials.homeServer; apiPathPrefix = kMXAPIPrefixPathR0; + antivirusServerPathPrefix = kMXAntivirusAPIPrefixPathUnstable; contentPathPrefix = kMXContentPrefixPath; self.credentials = inCredentials; @@ -221,6 +228,7 @@ - (void)close homeserverSuffix = nil; httpClient = nil; identityHttpClient = nil; + antivirusHttpClient = nil; processingQueue = nil; completionQueue = nil; @@ -3448,6 +3456,157 @@ - (MXHTTPOperation*)signUrl:(NSString*)signUrl }]; } +#pragma mark - Antivirus server API +- (void)setAntivirusServer:(NSString *)antivirusServer +{ + if (antivirusServer.length) + { + _antivirusServer = [antivirusServer copy]; + antivirusHttpClient = [[MXHTTPClient alloc] initWithBaseURL:[NSString stringWithFormat:@"%@/%@", antivirusServer, antivirusServerPathPrefix] + andOnUnrecognizedCertificateBlock:nil]; + } + else + { + // Disable antivirus requests + _antivirusServer = nil; + antivirusHttpClient = nil; + } +} + +- (MXHTTPOperation*)getAntivirusServerPublicKey:(void (^)(NSString *publicKey))success + failure:(void (^)(NSError *error))failure +{ + MXWeakify(self); + return [antivirusHttpClient requestWithMethod:@"GET" + path:@"public_key" + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + if (success) + { + __block NSString *publicKey; + [self dispatchProcessing:^{ + MXJSONModelSetString(publicKey, JSONResponse[@"public_key"]); + } andCompletion:^{ + success(publicKey); + }]; + } + } + failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)scanUnencryptedContent:(NSString*)mxcContentURI + success:(void (^)(MXContentScanResult *scanResult))success + failure:(void (^)(NSError *error))failure +{ + // Sanity check + if (![mxcContentURI hasPrefix:kMXContentUriScheme]) + { + // do not scan non-mxc content URLs + return nil; + } + + // Build request path by replacing the "mxc://" scheme + NSString *path = [mxcContentURI stringByReplacingOccurrencesOfString:kMXContentUriScheme withString:@"scan/"]; + MXWeakify(self); + return [antivirusHttpClient requestWithMethod:@"GET" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + if (success) + { + __block MXContentScanResult *scanResult; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(scanResult, MXContentScanResult, JSONResponse); + } andCompletion:^{ + success(scanResult); + }]; + } + } + failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)scanEncryptedContent:(MXEncryptedContentFile*)encryptedContentFile + success:(void (^)(MXContentScanResult *scanResult))success + failure:(void (^)(NSError *error))failure +{ + NSData *payloadData = nil; + if (encryptedContentFile) + { + payloadData = [NSJSONSerialization dataWithJSONObject:@{@"file": encryptedContentFile.JSONDictionary} options:0 error:nil]; + } + + MXWeakify(self); + return [antivirusHttpClient requestWithMethod:@"POST" + path:@"scan_encrypted" + parameters:nil + data:payloadData + headers:@{@"Content-Type": @"application/json"} + timeout:-1 + uploadProgress:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + if (success) + { + __block MXContentScanResult *scanResult; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(scanResult, MXContentScanResult, JSONResponse); + } andCompletion:^{ + success(scanResult); + }]; + } + } + failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)scanEncryptedContentWithSecureExchange:(MXContentScanEncryptedBody *)encryptedbody + success:(void (^)(MXContentScanResult *scanResult))success + failure:(void (^)(NSError *error))failure +{ + NSData *payloadData = nil; + if (encryptedbody) + { + payloadData = [NSJSONSerialization dataWithJSONObject:@{@"encrypted_body": encryptedbody.JSONDictionary} options:0 error:nil]; + } + + MXWeakify(self); + return [antivirusHttpClient requestWithMethod:@"POST" + path:@"scan_encrypted" + parameters:nil + data:payloadData + headers:@{@"Content-Type": @"application/json"} + timeout:-1 + uploadProgress:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + if (success) + { + __block MXContentScanResult *scanResult; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(scanResult, MXContentScanResult, JSONResponse); + } andCompletion:^{ + success(scanResult); + }]; + } + } + failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +#pragma mark - Certificates + -(void)setPinnedCertificates:(NSSet *)pinnedCertificates { httpClient.pinnedCertificates = pinnedCertificates; } @@ -3742,6 +3901,372 @@ - (MXHTTPOperation *)keyChangesFrom:(NSString *)fromToken to:(NSString *)toToken } +#pragma mark - Crypto: e2e keys backup +- (MXHTTPOperation*)createKeyBackupVersion:(MXKeyBackupVersion*)keyBackupVersion + success:(void (^)(NSString *version))success + failure:(void (^)(NSError *error))failure +{ + MXWeakify(self); + return [httpClient requestWithMethod:@"POST" + path:[NSString stringWithFormat:@"%@/room_keys/version", kMXAPIPrefixPathUnstable] + parameters:keyBackupVersion.JSONDictionary + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + __block NSString *version; + [self dispatchProcessing:^{ + MXJSONModelSetString(version, JSONResponse[@"version"]); + } andCompletion:^{ + success(version); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)keyBackupVersion:(void (^)(MXKeyBackupVersion *keyBackupVersion))success + failure:(void (^)(NSError *error))failure +{ + MXWeakify(self); + return [httpClient requestWithMethod:@"GET" + path:[NSString stringWithFormat:@"%@/room_keys/version", kMXAPIPrefixPathUnstable] + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + __block MXKeyBackupVersion *keyBackupVersion; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(keyBackupVersion, MXKeyBackupVersion, JSONResponse); + } andCompletion:^{ + success(keyBackupVersion); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)sendKeyBackup:(MXKeyBackupData*)keyBackupData + room:(NSString*)roomId + session:(NSString*)sessionId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:roomId session:sessionId version:version]; + if (!path || !keyBackupData || !roomId || !sessionId) + { + NSLog(@"[MXRestClient] sendKeyBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + return [self sendBackup:keyBackupData.JSONDictionary path:path success:success failure:failure]; +} + +- (MXHTTPOperation*)sendRoomKeysBackup:(MXRoomKeysBackupData*)roomKeysBackupData + room:(NSString*)roomId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:roomId session:nil version:version]; + if (!path || !roomKeysBackupData || !roomId) + { + NSLog(@"[MXRestClient] sendRoomKeysBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + return [self sendBackup:roomKeysBackupData.JSONDictionary path:path success:success failure:failure]; +} + +- (MXHTTPOperation*)sendKeysBackup:(MXKeysBackupData*)keysBackupData + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:nil session:nil version:version]; + if (!path || !keysBackupData) + { + NSLog(@"[MXRestClient] sendKeysBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + return [self sendBackup:keysBackupData.JSONDictionary path:path success:success failure:failure]; +} + +- (MXHTTPOperation*)sendBackup:(NSDictionary*)backupData + path:(NSString*)path + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + MXWeakify(self); + return [httpClient requestWithMethod:@"PUT" + path:path + parameters:backupData + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + [self dispatchProcessing:nil + andCompletion:^{ + success(); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)keyBackupForSession:(NSString*)sessionId + inRoom:(NSString*)roomId + version:(NSString*)version + success:(void (^)(MXKeyBackupData *keyBackupData))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:roomId session:sessionId version:version]; + if (!path || !roomId || !sessionId) + { + NSLog(@"[MXRestClient] keyBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + MXWeakify(self); + return [httpClient requestWithMethod:@"GET" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + __block MXKeyBackupData *keyBackupData; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(keyBackupData, MXKeyBackupData, JSONResponse); + } andCompletion:^{ + success(keyBackupData); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)keysBackupInRoom:(NSString*)roomId + version:(NSString*)version + success:(void (^)(MXRoomKeysBackupData *roomKeysBackupData))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:roomId session:nil version:version]; + if (!path || !roomId) + { + NSLog(@"[MXRestClient] roomKeysBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + MXWeakify(self); + return [httpClient requestWithMethod:@"GET" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + __block MXRoomKeysBackupData *roomKeysBackupData; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(roomKeysBackupData, MXRoomKeysBackupData, JSONResponse); + } andCompletion:^{ + success(roomKeysBackupData); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)keysBackup:(NSString*)version + success:(void (^)(MXKeysBackupData *keysBackupData))success + failure:(void (^)(NSError *error))failure; +{ + NSString *path = [self keyBackupPath:nil session:nil version:version]; + if (!path) + { + NSLog(@"[MXRestClient] keysBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + MXWeakify(self); + return [httpClient requestWithMethod:@"GET" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + __block MXKeysBackupData *keysBackupData; + [self dispatchProcessing:^{ + MXJSONModelSetMXJSONModel(keysBackupData, MXKeysBackupData, JSONResponse); + } andCompletion:^{ + success(keysBackupData); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)deleteKeyFromBackup:(NSString*)roomId + session:(NSString*)sessionId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:roomId session:sessionId version:version]; + if (!path || !roomId || !sessionId) + { + NSLog(@"[MXRestClient] deleteKeyFromBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + MXWeakify(self); + return [httpClient requestWithMethod:@"DELETE" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + [self dispatchProcessing:nil + andCompletion:^{ + success(); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)deleteKeysInRoomFromBackup:(NSString*)roomId + version:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:roomId session:nil version:version]; + if (!path || !roomId) + { + NSLog(@"[MXRestClient] deleteKeysInRoomFromBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + MXWeakify(self); + return [httpClient requestWithMethod:@"DELETE" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + [self dispatchProcessing:nil + andCompletion:^{ + success(); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (MXHTTPOperation*)deleteKeysFromBackup:(NSString*)version + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + NSString *path = [self keyBackupPath:nil session:nil version:version]; + if (!path) + { + NSLog(@"[MXRestClient] keysBackup: ERROR: Bad parameters"); + [self dispatchFailure:nil inBlock:failure]; + return nil; + } + + MXWeakify(self); + return [httpClient requestWithMethod:@"DELETE" + path:path + parameters:nil + success:^(NSDictionary *JSONResponse) { + MXStrongifyAndReturnIfNil(self); + + if (success) + { + [self dispatchProcessing:nil + andCompletion:^{ + success(); + }]; + } + } failure:^(NSError *error) { + MXStrongifyAndReturnIfNil(self); + [self dispatchFailure:error inBlock:failure]; + }]; +} + +- (NSString*)keyBackupPath:(NSString*)roomId session:(NSString*)sessionId version:(NSString*)version +{ + if (!version) + { + return nil; + } + + NSMutableString *path = [NSMutableString stringWithFormat:@"%@/room_keys/keys", kMXAPIPrefixPathUnstable]; + + if (sessionId) + { + if (!roomId) + { + NSLog(@"[MXRestClient] keyBackupPath: ERROR: Null version"); + return nil; + } + [path appendString:@"/"]; + [path appendString:[roomId stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + [path appendString:@"/"]; + [path appendString:[sessionId stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + } + else if (roomId) + { + [path appendString:@"/"]; + [path appendString:[roomId stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; + } + + [path appendString:@"?version="]; + [path appendString:version]; + + return path; +} + + #pragma mark - Direct-to-device messaging - (MXHTTPOperation*)sendToDevice:(NSString*)eventType contentMap:(MXUsersDevicesMap*)contentMap txnId:(NSString*)txnId diff --git a/MatrixSDK/MXSession.h b/MatrixSDK/MXSession.h index a280448f85..a06d45c1e4 100644 --- a/MatrixSDK/MXSession.h +++ b/MatrixSDK/MXSession.h @@ -638,6 +638,11 @@ typedef void (^MXOnBackgroundSyncFail)(NSError *error); */ - (MXHTTPOperation*)supportedMatrixVersions:(void (^)(MXMatrixVersions *matrixVersions))success failure:(void (^)(NSError *error))failure; +/** + The antivirus server URL (nil by default). + Set a non-null url to configure the antivirus scanner use. + */ +@property (nonatomic) NSString *antivirusServerURL; #pragma mark - Rooms operations /** diff --git a/MatrixSDK/MXSession.m b/MatrixSDK/MXSession.m index 61973d1555..5fc6b91357 100644 --- a/MatrixSDK/MXSession.m +++ b/MatrixSDK/MXSession.m @@ -1568,6 +1568,14 @@ - (MXHTTPOperation*)supportedMatrixVersions:(void (^)(MXMatrixVersions *))succes return [matrixRestClient supportedMatrixVersions:success failure:failure]; } +- (void)setAntivirusServerURL:(NSString *)antivirusServerURL +{ + _antivirusServerURL = antivirusServerURL; + // Update the current restClient + [matrixRestClient setAntivirusServer:antivirusServerURL]; + + // TODO: configure here a scan manager, and update the media manager. +} #pragma mark - Rooms operations diff --git a/MatrixSDK/Utils/MXTools.h b/MatrixSDK/Utils/MXTools.h index 35ac769d9e..d646381704 100644 --- a/MatrixSDK/Utils/MXTools.h +++ b/MatrixSDK/Utils/MXTools.h @@ -60,6 +60,15 @@ */ + (NSString*)stripNewlineCharacters:(NSString *)inputString; +/** + Add a white space every given number of characters. + + @param inputString the original string. + @param characters number of characters between each white space. + @return a string with white spaces. + */ ++ (NSString*)addWhiteSpacesToString:(NSString *)inputString every:(NSUInteger)characters; + #pragma mark - Strings kinds check diff --git a/MatrixSDK/Utils/MXTools.m b/MatrixSDK/Utils/MXTools.m index d3890c7292..de3f66cba2 100644 --- a/MatrixSDK/Utils/MXTools.m +++ b/MatrixSDK/Utils/MXTools.m @@ -271,6 +271,29 @@ + (NSString*)stripNewlineCharacters:(NSString *)inputString return string; } ++ (NSString*)addWhiteSpacesToString:(NSString *)inputString every:(NSUInteger)characters +{ + NSMutableString *whiteSpacedString = [NSMutableString new]; + for (int i = 0; i < inputString.length / characters + 1; i++) + { + NSUInteger fromIndex = i * characters; + NSUInteger len = inputString.length - fromIndex; + if (len > characters) + { + len = characters; + } + + NSString *whiteFormat = @"%@ "; + if (fromIndex + characters >= inputString.length) + { + whiteFormat = @"%@"; + } + [whiteSpacedString appendFormat:whiteFormat, [inputString substringWithRange:NSMakeRange(fromIndex, len)]]; + } + + return whiteSpacedString; +} + #pragma mark - String kinds check diff --git a/MatrixSDKTests/MXCryptoBackupTests.m b/MatrixSDKTests/MXCryptoBackupTests.m new file mode 100644 index 0000000000..765eaf63c2 --- /dev/null +++ b/MatrixSDKTests/MXCryptoBackupTests.m @@ -0,0 +1,817 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "MatrixSDKTestsData.h" +#import "MatrixSDKTestsE2EData.h" + +#import "MXCrypto_Private.h" +#import "MXRecoveryKey.h" + +@interface MXKeyBackup (Testing) + +- (OLMPkDecryption*)pkDecryptionFromRecoveryKey:(NSString*)recoveryKey error:(NSError **)error; +- (MXKeyBackupData*)encryptGroupSession:(MXOlmInboundGroupSession*)session; +- (MXMegolmSessionData*)decryptKeyBackupData:(MXKeyBackupData*)keyBackupData forSession:(NSString*)sessionId inRoom:(NSString*)roomId withPkDecryption:(OLMPkDecryption*)decryption; + +@end + + +// Do not bother with retain cycles warnings in tests +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-retain-cycles" + +@interface MXCryptoBackupTests : XCTestCase +{ + MatrixSDKTestsData *matrixSDKTestsData; + MatrixSDKTestsE2EData *matrixSDKTestsE2EData; +} +@end + +@implementation MXCryptoBackupTests + +- (void)setUp +{ + [super setUp]; + + matrixSDKTestsData = [[MatrixSDKTestsData alloc] init]; + matrixSDKTestsE2EData = [[MatrixSDKTestsE2EData alloc] initWithMatrixSDKTestsData:matrixSDKTestsData]; +} + +- (void)tearDown +{ + matrixSDKTestsData = nil; + matrixSDKTestsE2EData = nil; + + [super tearDown]; +} + +- (MXKeyBackupVersion*)fakeKeyBackupVersion +{ + return [MXKeyBackupVersion modelFromJSON:@{ + @"algorithm": kMXCryptoMegolmBackupAlgorithm, + @"auth_data": @{ + @"public_key": @"abcdefg", + @"signatures": @{ + @"something": @{ + @"ed25519:something": @"hijklmnop" + } + } + } + }]; +} + + +/** + - Create a backup version on the server + - Get the current version from the server + - Check they match + */ +- (void)testRESTCreateKeyBackupVersion +{ + [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { + + // - Create a backup version on the server + MXKeyBackupVersion *keyBackupVersion = self.fakeKeyBackupVersion; + [aliceRestClient createKeyBackupVersion:keyBackupVersion success:^(NSString *version) { + + // - Get the current version from the server + [aliceRestClient keyBackupVersion:^(MXKeyBackupVersion *keyBackupVersion2) { + + // - Check they match + XCTAssertNotNil(keyBackupVersion2); + XCTAssertEqualObjects(keyBackupVersion2.version, version); + XCTAssertEqualObjects(keyBackupVersion2.algorithm, keyBackupVersion.algorithm); + XCTAssertEqualObjects(keyBackupVersion2.authData, keyBackupVersion.authData); + + [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]; + }]; + }]; +} + +/** + - Create a backup version on the server + - Make a backup + - Get the backup back + -> Check they match + */ +- (void)testRESTBackupKeys +{ + [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { + + // - Create a backup version on the server + [aliceRestClient createKeyBackupVersion:self.fakeKeyBackupVersion success:^(NSString *version) { + + //- Make a backup + MXKeyBackupData *keyBackupData = [MXKeyBackupData new]; + keyBackupData.firstMessageIndex = 1; + keyBackupData.forwardedCount = 2; + keyBackupData.verified = YES; + keyBackupData.sessionData = @{ + @"key": @"value" + }; + + NSString *roomId = @"!aRoomId:matrix.org"; + NSString *sessionId = @"ASession"; + + [aliceRestClient sendKeyBackup:keyBackupData room:roomId session:sessionId version:version success:^{ + + // - Get the backup back + [aliceRestClient keysBackup:version success:^(MXKeysBackupData *keysBackupData) { + + // -> Check they match + MXKeyBackupData *keyBackupData2 = keysBackupData.rooms[roomId].sessions[sessionId]; + XCTAssertNotNil(keyBackupData2); + XCTAssertEqualObjects(keyBackupData2.JSONDictionary, keyBackupData.JSONDictionary); + + [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]; + }]; + }]; +} + +/** + - Create a backup version on the server + - Make a backup + - Delete it + - Get the backup back + -> Check it is now empty + */ +- (void)testRESTDeleteBackupKeys +{ + [matrixSDKTestsData doMXRestClientTestWithAlice:self readyToTest:^(MXRestClient *aliceRestClient, XCTestExpectation *expectation) { + + // - Create a backup version on the server + [aliceRestClient createKeyBackupVersion:self.fakeKeyBackupVersion success:^(NSString *version) { + + //- Make a backup + MXKeyBackupData *keyBackupData = [MXKeyBackupData new]; + keyBackupData.firstMessageIndex = 1; + keyBackupData.forwardedCount = 2; + keyBackupData.verified = YES; + keyBackupData.sessionData = @{ + @"key": @"value" + }; + + NSString *roomId = @"!aRoomId:matrix.org"; + NSString *sessionId = @"ASession"; + + [aliceRestClient sendKeyBackup:keyBackupData room:roomId session:sessionId version:version success:^{ + + // - Delete it + [aliceRestClient deleteKeyFromBackup:roomId session:sessionId version:version success:^{ + + // - Get the backup back + [aliceRestClient keysBackup:version success:^(MXKeysBackupData *keysBackupData) { + + // -> Check it is now empty + XCTAssertNotNil(keysBackupData); + XCTAssertEqual(keysBackupData.rooms.count, 0); + + [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]; + }]; + }]; +} + + +/** +- From doE2ETestWithAliceAndBobInARoomWithCryptedMessages, we should have no backed up keys +- Check backup keys after having marked one as backed up +- Reset keys backup markers +*/ +- (void)testBackupStore +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + // - From doE2ETestWithAliceAndBobInARoomWithCryptedMessages, we should have no backed up keys + NSArray *sessions = [aliceSession.crypto.store inboundGroupSessionsToBackup:100]; + NSUInteger sessionsCount = sessions.count; + XCTAssertGreaterThan(sessionsCount, 0); + XCTAssertEqual([aliceSession.crypto.store inboundGroupSessionsCount:NO], sessionsCount); + XCTAssertEqual([aliceSession.crypto.store inboundGroupSessionsCount:YES], 0); + + // - Check backup keys after having marked one as backed up + MXOlmInboundGroupSession *session = sessions.firstObject; + [aliceSession.crypto.store markBackupDoneForInboundGroupSessionWithId:session.session.sessionIdentifier andSenderKey:session.senderKey]; + sessions = [aliceSession.crypto.store inboundGroupSessionsToBackup:100]; + XCTAssertEqual(sessions.count, sessionsCount - 1); + XCTAssertEqual([aliceSession.crypto.store inboundGroupSessionsCount:NO], sessionsCount); + XCTAssertEqual([aliceSession.crypto.store inboundGroupSessionsCount:YES], 1); + + // - Reset keys backup markers + [aliceSession.crypto.store resetBackupMarkers]; + sessions = [aliceSession.crypto.store inboundGroupSessionsToBackup:100]; + XCTAssertEqual(sessions.count, sessionsCount); + XCTAssertEqual([aliceSession.crypto.store inboundGroupSessionsCount:NO], sessionsCount); + XCTAssertEqual([aliceSession.crypto.store inboundGroupSessionsCount:YES], 0); + + [expectation fulfill]; + }]; +} + +/** + - Check [MXRecoveryKey encode:] + - Check [MXRecoveryKey decode:error:] with a valid recovery key + - Check [MXRecoveryKey decode:error:] with an invalid recovery key + */ +- (void)testRecoveryKey +{ + UInt8 privateKeyBytes[] = { + 0x77, 0x07, 0x6D, 0x0A, 0x73, 0x18, 0xA5, 0x7D, + 0x3C, 0x16, 0xC1, 0x72, 0x51, 0xB2, 0x66, 0x45, + 0xDF, 0x4C, 0x2F, 0x87, 0xEB, 0xC0, 0x99, 0x2A, + 0xB1, 0x77, 0xFB, 0xA5, 0x1D, 0xB9, 0x2C, 0x2A + }; + NSData *privateKey = [NSData dataWithBytes:privateKeyBytes length:sizeof(privateKeyBytes)]; + + // Got this value from js console with recoveryKey.js:encodeRecoveryKey + NSString *recoveryKey = @"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d"; + + // - Check [MXRecoveryKey encode:] + NSString *recoveryKeyOut = [MXRecoveryKey encode:privateKey]; + XCTAssertEqualObjects(recoveryKeyOut, recoveryKey); + + // - Check [MXRecoveryKey decode:error:] with a valid recovery key + NSError *error; + NSData *privateKeyOut = [MXRecoveryKey decode:recoveryKey error:&error]; + XCTAssertNil(error); + XCTAssertEqualObjects(privateKeyOut, privateKey); + + // - Check [MXRecoveryKey decode:error:] with an invalid recovery key + NSString *badRecoveryKey = [recoveryKey stringByReplacingOccurrencesOfString:@"UE4d" withString:@"UE4e"]; + privateKeyOut = [MXRecoveryKey decode:badRecoveryKey error:&error]; + XCTAssertNil(privateKeyOut); + XCTAssertEqualObjects(error.domain, MXRecoveryKeyErrorDomain); +} + +- (void)testIsValidRecoveryKey +{ + NSString *recoveryKey = @"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d"; + NSString *invalidRecoveryKey1 = @"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4e"; + 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:recoveryKey]); + XCTAssertFalse([MXKeyBackup isValidRecoveryKey:invalidRecoveryKey1]); + XCTAssertFalse([MXKeyBackup isValidRecoveryKey:invalidRecoveryKey2]); + XCTAssertFalse([MXKeyBackup isValidRecoveryKey:invalidRecoveryKey3]); +} + +/** + Check that `[MXKeyBackup prepareKeyBackupVersion` returns valid data + */ +- (void)testPrepareKeyBackupVersion +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + XCTAssertNotNil(aliceSession.crypto.backup); + XCTAssertFalse(aliceSession.crypto.backup.enabled); + + // Check that `[MXKeyBackup prepareKeyBackupVersion` returns valid data + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + + XCTAssertNotNil(keyBackupCreationInfo); + XCTAssertEqualObjects(keyBackupCreationInfo.algorithm, kMXCryptoMegolmBackupAlgorithm); + XCTAssertNotNil(keyBackupCreationInfo.authData.publicKey); + XCTAssertNotNil(keyBackupCreationInfo.authData.signatures); + XCTAssertNotNil(keyBackupCreationInfo.recoveryKey); + + [expectation fulfill]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + Check that `[MXKeyBackup createKeyBackupVersion` returns valid data + */ +- (void)testCreateKeyBackupVersion +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + XCTAssertFalse(aliceSession.crypto.backup.enabled); + + // Check that `[MXKeyBackup createKeyBackupVersion` returns valid data + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + + XCTAssertEqualObjects(keyBackupVersion.algorithm, kMXCryptoMegolmBackupAlgorithm); + XCTAssertEqualObjects(keyBackupVersion.authData, keyBackupCreationInfo.authData.JSONDictionary); + XCTAssertNotNil(keyBackupVersion.version); + + // Backup must be enable now + XCTAssertTrue(aliceSession.crypto.backup.enabled); + + [expectation fulfill]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + - Check that `[MXKeyBackup createKeyBackupVersion` launches the backup + - Check the backup completes + */ +- (void)testBackupAfterCreateKeyBackupVersion +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + + // Check that `[MXKeyBackup createKeyBackupVersion` launches the backup + XCTAssert(aliceSession.crypto.backup.state == MXKeyBackupStateEnabling + || aliceSession.crypto.backup.state == MXKeyBackupStateWillBackUp); + + NSUInteger keys = [aliceSession.crypto.store inboundGroupSessionsCount:NO]; + + __block id observer; + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKeyBackupDidStateChangeNotification object:aliceSession.crypto.backup queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + // Check the backup completes + if (observer && aliceSession.crypto.backup.state == MXKeyBackupStateReadyToBackUp) + { + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + observer = nil; + + NSUInteger backedUpkeys = [aliceSession.crypto.store inboundGroupSessionsCount:YES]; + XCTAssertEqual(backedUpkeys, keys, @"All keys must have been marked as backed up"); + + [expectation fulfill]; + } + }]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + - Create a backup version + - Check the returned MXKeyBackupVersion is trusted + */ +- (void)testIsKeyBackupTrusted +{ + // - Create a backup version + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + + // - Check the returned MXKeyBackupVersion is trusted + [aliceSession.crypto.backup isKeyBackupTrusted:keyBackupVersion onComplete:^(MXKeyBackupVersionTrust * _Nonnull keyBackupVersionTrust) { + + XCTAssertNotNil(keyBackupVersionTrust); + XCTAssertTrue(keyBackupVersionTrust.usable); + + XCTAssertEqual(keyBackupVersionTrust.signatures.count, 1); + + MXKeyBackupVersionTrustSignature *signature = keyBackupVersionTrust.signatures.firstObject; + XCTAssertTrue(signature.valid); + XCTAssertEqualObjects(signature.device.deviceId, aliceSession.matrixRestClient.credentials.deviceId); + + [expectation fulfill]; + }]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + Check that `[MXKeyBackup backupAllGroupSessions]` returns valid data + */ +- (void)testBackupAllGroupSessions +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + // Check that `[MXKeyBackup backupAllGroupSessions]` returns valid data + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + + NSUInteger keys = [aliceSession.crypto.store inboundGroupSessionsCount:NO]; + __block NSUInteger lastbackedUpkeysProgress = 0; + + [aliceSession.crypto.backup backupAllGroupSessions:^{ + + NSUInteger backedUpkeys = [aliceSession.crypto.store inboundGroupSessionsCount:YES]; + XCTAssertEqual(backedUpkeys, keys, @"All keys must have been marked as backed up"); + + XCTAssertEqual(lastbackedUpkeysProgress, keys); + + [expectation fulfill]; + + } progress:^(NSProgress * _Nonnull backupProgress) { + + XCTAssertEqual(backupProgress.totalUnitCount, keys); + lastbackedUpkeysProgress = backupProgress.completedUnitCount; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + Check encryption and decryption of megolm keys in the backup. + - Pick a megolm key + - Check [MXKeyBackup encryptGroupSession] returns stg + - Check [MXKeyBackup pkDecryptionFromRecoveryKey] is able to create a OLMPkDecryption + - Check [MXKeyBackup decryptKeyBackupData] returns stg + - Compare the decrypted megolm key with the original one + */ +- (void)testEncryptAndDecryptKeyBackupData +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + // - Pick a megolm key + MXOlmInboundGroupSession *session = [aliceSession.crypto.store inboundGroupSessionsToBackup:1].firstObject; + + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + + // - Check [MXKeyBackup encryptGroupSession] returns stg + MXKeyBackupData *keyBackupData = [aliceSession.crypto.backup encryptGroupSession:session]; + XCTAssertNotNil(keyBackupData); + XCTAssertNotNil(keyBackupData.sessionData); + + // - Check [MXKeyBackup pkDecryptionFromRecoveryKey] is able to create a OLMPkDecryption + OLMPkDecryption *decryption = [aliceSession.crypto.backup pkDecryptionFromRecoveryKey:keyBackupCreationInfo.recoveryKey error:nil]; + XCTAssertNotNil(decryption); + + // - Check [MXKeyBackup decryptKeyBackupData] returns stg + MXMegolmSessionData *sessionData = [aliceSession.crypto.backup decryptKeyBackupData:keyBackupData forSession:session.session.sessionIdentifier inRoom:roomId withPkDecryption:decryption]; + XCTAssertNotNil(sessionData); + + // - Compare the decrypted megolm key with the original one + XCTAssertEqualObjects(session.exportSessionData.JSONDictionary, sessionData.JSONDictionary); + + [expectation fulfill]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + - Do an e2e backup to the homeserver + - Log Alice on a new device + - Restore the e2e backup from the homeserver + - Imported keys number must be correct + - The new device must have the same count of megolm keys + - Alice must have the same keys on both devices + */ +- (void)testRestoreKeyBackup +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + NSArray *aliceKeys1 = [aliceSession.crypto.store inboundGroupSessionsToBackup:100]; + + // - Do an e2e backup to the homeserver + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + [aliceSession.crypto.backup backupAllGroupSessions:^{ + + // - Log Alice on a new device + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; + [matrixSDKTestsData relogUserSessionWithNewDevice:aliceSession withPassword:MXTESTS_ALICE_PWD onComplete:^(MXSession *aliceSession2) { + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; + + // Test check: aliceSession2 has no keys at login + XCTAssertEqual([aliceSession2.crypto.store inboundGroupSessionsCount:NO], 0); + + // - Restore the e2e backup from the homeserver + [aliceSession2.crypto.backup restoreKeyBackup:keyBackupVersion.version + recoveryKey:keyBackupCreationInfo.recoveryKey + room:nil session:nil + success:^(NSUInteger total, NSUInteger imported) + { + // - Imported keys number must be correct + XCTAssertEqual(total, aliceKeys1.count); + XCTAssertEqual(total, imported); + + // - The new device must have the same count of megolm keys + XCTAssertEqual([aliceSession2.crypto.store inboundGroupSessionsCount:NO], aliceKeys1.count); + + // - Alice must have the same keys on both devices + for (MXOlmInboundGroupSession *aliceKey1 in aliceKeys1) + { + MXOlmInboundGroupSession *aliceKey2 = [aliceSession2.crypto.store inboundGroupSessionWithId:aliceKey1.session.sessionIdentifier andSenderKey:aliceKey1.senderKey]; + XCTAssertNotNil(aliceKey2); + XCTAssertEqualObjects(aliceKey2.exportSessionData.JSONDictionary, aliceKey1.exportSessionData.JSONDictionary); + } + + [expectation fulfill]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; + } progress:nil failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + Check backup starts automatically if there is an existing and compatible backup + version on the homeserver. + - Create a backup version + - Restart alice session + -> The new alice session must back up to the same version + */ +- (void)testCheckAndStartKeyBackupWhenRestartingAMatrixSession +{ + // - Create a backup version + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + XCTAssertFalse(aliceSession.crypto.backup.enabled); + + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + + XCTAssertTrue(aliceSession.crypto.backup.enabled); + + // - Restart alice session + MXSession *aliceSession2 = [[MXSession alloc] initWithMatrixRestClient:aliceSession.matrixRestClient]; + [aliceSession close]; + [aliceSession2 start:nil failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + // -> The new alice session must back up to the same version + __block id observer; + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKeyBackupDidStateChangeNotification object:aliceSession2.crypto.backup queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + if (observer && aliceSession2.crypto.backup.state == MXKeyBackupStateReadyToBackUp) + { + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + observer = nil; + + XCTAssertEqualObjects(aliceSession2.crypto.backup.keyBackupVersion.version, keyBackupVersion.version); + + [expectation fulfill]; + } + }]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + Check MXKeyBackupStateWrongBackUpVersion state + - Make alice back up her keys to her homeserver + - Create a new backup with fake data on the homeserver + - Make alice back up all her keys again + -> That must fail and her backup state must be disabled + */ +- (void)testBackupWhenAnotherBackupWasCreated +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + // - Make alice back up her keys to her homeserver + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + + XCTAssertTrue(aliceSession.crypto.backup.enabled); + + // - Create a new backup with fake data on the homeserver + [aliceSession.matrixRestClient createKeyBackupVersion:self.fakeKeyBackupVersion success:^(NSString *version) { + + // - Make alice back up all her keys again + [aliceSession.crypto.backup backupAllGroupSessions:^{ + + XCTFail(@"The backup must fail"); + [expectation fulfill]; + + } progress:nil failure:^(NSError * _Nonnull error) { + + // -> That must fail and her backup state must be disabled + XCTAssertEqual(aliceSession.crypto.backup.state, MXKeyBackupStateWrongBackUpVersion); + XCTAssertFalse(aliceSession.crypto.backup.enabled); + + [expectation fulfill]; + }]; + + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +/** + - Do an e2e backup to the homeserver + - Log Alice on a new device + - Post a message to have a new megolm session + - Try to backup all + -> It must fail + - Validate the old device from the new one + -> Backup should automatically enable on the new device + -> It must use the same backup version + - Try to backup all again + -> It must success + */ +- (void)testBackupAfterVerifyingADevice +{ + [matrixSDKTestsE2EData doE2ETestWithAliceAndBobInARoomWithCryptedMessages:self cryptedBob:YES readyToTest:^(MXSession *aliceSession, MXSession *bobSession, NSString *roomId, XCTestExpectation *expectation) { + + // - Do an e2e backup to the homeserver + [aliceSession.crypto.backup prepareKeyBackupVersion:^(MXMegolmBackupCreationInfo * _Nonnull keyBackupCreationInfo) { + [aliceSession.crypto.backup createKeyBackupVersion:keyBackupCreationInfo success:^(MXKeyBackupVersion * _Nonnull keyBackupVersion) { + [aliceSession.crypto.backup backupAllGroupSessions:^{ + + NSString *oldDeviceId = aliceSession.matrixRestClient.credentials.deviceId; + MXKeyBackupVersion *oldKeyBackupVersion = aliceSession.crypto.backup.keyBackupVersion; + + // - Log Alice on a new device + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = YES; + [matrixSDKTestsData relogUserSessionWithNewDevice:aliceSession withPassword:MXTESTS_ALICE_PWD onComplete:^(MXSession *aliceSession2) { + [MXSDKOptions sharedInstance].enableCryptoWhenStartingMXSession = NO; + + // - Post a message to have a new megolm session + aliceSession2.crypto.warnOnUnknowDevices = NO; + MXRoom *room2 = [aliceSession2 roomWithRoomId:roomId]; + [room2 sendTextMessage:@"New keys" success:^(NSString *eventId) { + + // - Try to backup all + [aliceSession2.crypto.backup backupAllGroupSessions:^{ + + XCTFail(@"The backup must fail"); + [expectation fulfill]; + + } progress:nil failure:^(NSError * _Nonnull error) { + + // -> It must fail + XCTAssertEqualObjects(error.domain, MXKeyBackupErrorDomain); + XCTAssertEqual(error.code, MXKeyBackupErrorInvalidStateCode); + XCTAssertFalse(aliceSession2.crypto.backup.enabled); + + // - Validate the old device from the new one + [aliceSession2.crypto setDeviceVerification:MXDeviceVerified forDevice:oldDeviceId ofUser:aliceSession2.myUser.userId success:nil failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + + // -> Backup should automatically enable on the new device + __block id observer; + observer = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKeyBackupDidStateChangeNotification object:aliceSession2.crypto.backup queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + + if (observer && aliceSession2.crypto.backup.state == MXKeyBackupStateReadyToBackUp) + { + [[NSNotificationCenter defaultCenter] removeObserver:observer]; + observer = nil; + + // -> It must use the same backup version + XCTAssertEqualObjects(oldKeyBackupVersion.version, aliceSession2.crypto.backup.keyBackupVersion.version); + + // - Try to backup all again + [aliceSession2.crypto.backup backupAllGroupSessions:^{ + + // -> It must success + XCTAssertTrue(aliceSession2.crypto.backup.enabled); + + [expectation fulfill]; + + } progress:nil failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } + }]; + }]; + } failure:^(NSError *error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; + } progress:nil failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + } failure:^(NSError * _Nonnull error) { + XCTFail(@"The request should not fail - NSError: %@", error); + [expectation fulfill]; + }]; + }]; +} + +@end + +#pragma clang diagnostic pop diff --git a/MatrixSDKTests/MXCryptoTests.m b/MatrixSDKTests/MXCryptoTests.m index ef3f37788c..3ee5fef082 100644 --- a/MatrixSDKTests/MXCryptoTests.m +++ b/MatrixSDKTests/MXCryptoTests.m @@ -2341,7 +2341,10 @@ - (void)testImportRoomKeys }]; // Import the exported keys - [bobSession.crypto importRoomKeys:keys success:^{ + [bobSession.crypto importRoomKeys:keys success:^(NSUInteger total, NSUInteger imported) { + + XCTAssertGreaterThan(total, 0); + XCTAssertEqual(total, imported); XCTAssertEqual(encryptedEvents.count, 0, @"All events should have been decrypted after the keys import"); @@ -2422,8 +2425,11 @@ - (void)testExportImportRoomKeysWithPassword }]; // Import the exported keys - [bobSession.crypto importRoomKeys:keyFile withPassword:password success:^{ + [bobSession.crypto importRoomKeys:keyFile withPassword:password success:^(NSUInteger total, NSUInteger imported) { + XCTAssertGreaterThan(total, 0); + XCTAssertEqual(total, imported); + XCTAssertEqual(encryptedEvents.count, 0, @"All events should have been decrypted after the keys import"); [expectation fulfill]; @@ -2468,7 +2474,7 @@ - (void)testImportRoomKeysWithWrongPassword [bobSession.crypto exportRoomKeysWithPassword:@"APassword" success:^(NSData *keyFile) { - [bobSession.crypto importRoomKeys:keyFile withPassword:@"AnotherPassword" success:^{ + [bobSession.crypto importRoomKeys:keyFile withPassword:@"AnotherPassword" success:^(NSUInteger total, NSUInteger imported) { XCTFail(@"The import must fail when using a wrong password"); [expectation fulfill]; diff --git a/Podfile b/Podfile index dd2c80fef0..5fda15054a 100644 --- a/Podfile +++ b/Podfile @@ -11,6 +11,7 @@ pod 'OLMKit', '~> 3.0.0', :inhibit_warnings => true #pod 'OLMKit', :path => '../olm/OLMKit.podspec' pod 'Realm', '~> 3.11.1' +pod 'libbase58', '~> 0.1.4' end diff --git a/Podfile.lock b/Podfile.lock index bf6c5d72c7..c5b180451b 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,6 +15,7 @@ PODS: - AFNetworking/UIKit (3.2.1): - AFNetworking/NSURLSession - GZIP (1.2.2) + - libbase58 (0.1.4) - OLMKit (3.0.0): - OLMKit/olmc (= 3.0.0) - OLMKit/olmcpp (= 3.0.0) @@ -27,6 +28,7 @@ PODS: DEPENDENCIES: - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.2) + - libbase58 (~> 0.1.4) - OLMKit (~> 3.0.0) - Realm (~> 3.11.1) @@ -34,15 +36,17 @@ SPEC REPOS: https://github.com/cocoapods/specs.git: - AFNetworking - GZIP + - libbase58 - OLMKit - Realm SPEC CHECKSUMS: AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 GZIP: 12374d285e3b5d46cfcd480700fcfc7e16caf4f1 + libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd OLMKit: 88eda69110489f817d59bcb4353b7c247570aa4f Realm: 037c5919b9ceb59d6beed5d3b031096856b119b3 -PODFILE CHECKSUM: bde5adf0c918613fa0f49b23ec06562ddba86eef +PODFILE CHECKSUM: 61c5af3ab100e2e410c011b64edf50bea12e675f COCOAPODS: 1.6.0.beta.2 diff --git a/SwiftMatrixSDK.podspec b/SwiftMatrixSDK.podspec index 899a590f65..adff26e1cb 100644 --- a/SwiftMatrixSDK.podspec +++ b/SwiftMatrixSDK.podspec @@ -31,5 +31,6 @@ Pod::Spec.new do |s| # Requirements for e2e encryption s.dependency 'OLMKit', '~> 3.0.0' s.dependency 'Realm', '~> 3.11.1' + s.dependency 'libbase58', '~> 0.1.4' end