diff --git a/CHANGES.rst b/CHANGES.rst index 4d6082923..18970e85f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,18 +4,6 @@ Changes in 0.12.0 (2020-xx-xx) Improvements: * Update deployment target to iOS 11.0. Required for Jitsi > 2.8.x. * Theme: Customize UISearchBar with new iOS 13 properties (#3270). - * PushNotificationService: Move all notification related code to a new class (PR #3100). - * Cross-signing: Bootstrap cross-sign on registration (and login if applicable). This action is now invisible to the user (#3292). - * Cross-signing: Setup cross-signing for existing users (#3299). - * Authentication: Redirect the webview (SSO) javascript logs to iOS native logs. - * Timeline: Hide encrypted history (pre-invite) (#3239). - * Complete security: Add recovery from 4S (#3304). - * Key backup: Connect/restore backup created with SSSS (#3124). - * E2E by default: Disable it if the HS admin disabled it (#3305). - * Key backup: Add secure backup creation flow (#3344). - * Add AuthenticatedSessionViewControllerFactory to set up a authenticated flow for a given CS API request. - * Set up SSSS from banners (#3293). - * NSE: Increase number of log files to 100. * NSE: Make extension reusable (#3326). Bug fix: @@ -39,11 +27,30 @@ Bug fix: * Xcode11: Fix session user display name (PR #3349). * Xcode11: Fix rebooted and unlocked case for NSE (PR #3353). * Xcode11: New localization keys for push notifications, include room display name in fallback content (#3325). - * CallVC: Declined calls now properly reset call view controller, thanks to @Legi429 (#2877). - * PreviewRoomTitleView: Fix inviter display name (#2520). * Xcode11: Disable voip background mode to avoid VoIP pushes (#3369). * Xcode11: Disable key backup on push extension (#3371). +Changes in 0.11.6 (2020-06-30) +=============================================== + +Improvements: + * Upgrade MatrixKit version ([v0.12.7](https://github.com/matrix-org/matrix-ios-kit/releases/tag/v0.12.7)). + * PushNotificationService: Move all notification related code to a new class (PR #3100). + * Cross-signing: Bootstrap cross-sign on registration (and login if applicable). This action is now invisible to the user (#3292). + * Cross-signing: Setup cross-signing for existing users (#3299). + * Authentication: Redirect the webview (SSO) javascript logs to iOS native logs. + * Timeline: Hide encrypted history (pre-invite) (#3239). + * Complete security: Add recovery from 4S (#3304). + * Key backup: Connect/restore backup created with SSSS (#3124). + * E2E by default: Disable it if the HS admin disabled it (#3305). + * Key backup: Add secure backup creation flow (#3344). + * Add AuthenticatedSessionViewControllerFactory to set up a authenticated flow for a given CS API request. + * Set up SSSS from banners (#3293). + +Bug fix: + * CallVC: Declined calls now properly reset call view controller, thanks to @Legi429 (#2877). + * PreviewRoomTitleView: Fix inviter display name (#2520). + Changes in 0.11.5 (2020-05-18) =============================================== diff --git a/Gemfile.lock b/Gemfile.lock index 9724fbefd..b995d32b2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,33 +2,33 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.2) - activesupport (4.2.11.1) + activesupport (4.2.11.3) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) - algoliasearch (1.27.1) + algoliasearch (1.27.3) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) aws-eventstream (1.1.0) - aws-partitions (1.322.0) - aws-sdk-core (3.97.0) + aws-partitions (1.337.0) + aws-sdk-core (3.102.1) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-kms (1.32.0) - aws-sdk-core (~> 3, >= 3.71.0) + aws-sdk-kms (1.35.0) + aws-sdk-core (~> 3, >= 3.99.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.67.0) - aws-sdk-core (~> 3, >= 3.96.1) + aws-sdk-s3 (1.72.0) + aws-sdk-core (~> 3, >= 3.102.1) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.4) - aws-eventstream (~> 1.0, >= 1.0.2) + aws-sigv4 (1.2.1) + aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.3) claide (1.0.3) cocoapods (1.8.4) @@ -57,21 +57,21 @@ GEM fuzzy_match (~> 2.0.4) nap (~> 1.0) cocoapods-deintegrate (1.0.4) - cocoapods-downloader (1.2.2) + cocoapods-downloader (1.3.0) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.0) cocoapods-stats (1.1.0) - cocoapods-trunk (1.4.1) + cocoapods-trunk (1.5.0) nap (>= 0.8, < 2.0) netrc (~> 0.11) - cocoapods-try (1.1.0) + cocoapods-try (1.2.0) colored (1.2) colored2 (3.1.2) commander-fastlane (4.4.6) highline (~> 1.7.2) - concurrent-ruby (1.1.5) - declarative (0.0.10) + concurrent-ruby (1.1.6) + declarative (0.0.20) declarative-option (0.1.0) digest-crc (0.5.1) domain_name (0.5.20190701) @@ -79,7 +79,7 @@ GEM dotenv (2.7.5) emoji_regex (1.0.1) escape (0.0.4) - excon (0.73.0) + excon (0.75.0) faraday (1.0.1) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) @@ -88,7 +88,7 @@ GEM faraday_middleware (1.0.0) faraday (~> 1.0) fastimage (2.1.7) - fastlane (2.148.1) + fastlane (2.149.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) aws-sdk-s3 (~> 1.0) @@ -151,7 +151,7 @@ GEM google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.12.0) + googleauth (0.13.0) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) @@ -165,12 +165,12 @@ GEM i18n (0.9.5) concurrent-ruby (~> 1.0) jmespath (1.4.0) - json (2.3.0) + json (2.3.1) jwt (2.1.0) memoist (0.16.2) mini_magick (4.10.1) mini_mime (1.0.2) - minitest (5.13.0) + minitest (5.14.1) molinillo (0.6.6) multi_json (1.14.1) multi_xml (0.6.0) @@ -208,7 +208,7 @@ GEM tty-screen (0.8.0) tty-spinner (0.9.3) tty-cursor (~> 0.7) - tzinfo (1.2.5) + tzinfo (1.2.7) thread_safe (~> 0.1) uber (0.1.0) unf (0.1.4) @@ -216,10 +216,10 @@ GEM unf_ext (0.0.7.7) unicode-display_width (1.7.0) word_wrap (1.0.0) - xcode-install (2.6.2) + xcode-install (2.6.6) claide (>= 0.9.1, < 1.1.0) fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.16.0) + xcodeproj (1.17.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) diff --git a/Podfile b/Podfile index b10731683..b12a75b09 100644 --- a/Podfile +++ b/Podfile @@ -7,7 +7,7 @@ use_frameworks! # Different flavours of pods to MatrixKit # The current MatrixKit pod version -$matrixKitVersion = '0.12.6' +$matrixKitVersion = '0.12.7' # The specific branch version (supported: develop, xcode11) $matrixKitVersion = 'xcode11' diff --git a/Podfile.lock b/Podfile.lock index c46201bb0..45e72022f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -50,38 +50,38 @@ PODS: - MatomoTracker (7.2.0): - MatomoTracker/Core (= 7.2.0) - MatomoTracker/Core (7.2.0) - - MatrixKit (0.12.6): + - MatrixKit (0.12.7): - cmark (~> 0.24.1) - - DTCoreText (~> 1.6.21) + - DTCoreText (~> 1.6.23) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.12.6) - - MatrixSDK (= 0.16.5) - - MatrixKit/AppExtension (0.12.6): + - MatrixKit/Core (= 0.12.7) + - MatrixSDK (= 0.16.6) + - MatrixKit/AppExtension (0.12.7): - cmark (~> 0.24.1) - - DTCoreText (~> 1.6.21) + - DTCoreText (~> 1.6.23) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.16.5) - - MatrixKit/Core (0.12.6): + - MatrixSDK (= 0.16.6) + - MatrixKit/Core (0.12.7): - cmark (~> 0.24.1) - - DTCoreText (~> 1.6.21) + - DTCoreText (~> 1.6.23) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.16.5) - - MatrixSDK (0.16.5): - - MatrixSDK/Core (= 0.16.5) - - MatrixSDK/Core (0.16.5): + - MatrixSDK (= 0.16.6) + - MatrixSDK (0.16.6): + - MatrixSDK/Core (= 0.16.6) + - MatrixSDK/Core (0.16.6): - AFNetworking (~> 4.0.0) - GZIP (~> 1.2.2) - libbase58 (~> 0.1.4) - OLMKit (~> 3.1.0) - Realm (~> 4.4.0) - - MatrixSDK/JingleCallStack (0.16.5): + - MatrixSDK/JingleCallStack (0.16.6): - JitsiMeetSDK (~> 2.3.1) - MatrixSDK/Core - - MatrixSDK/SwiftSupport (0.16.5): + - MatrixSDK/SwiftSupport (0.16.6): - MatrixSDK/Core - OLMKit (3.1.0): - OLMKit/olmc (= 3.1.0) @@ -109,8 +109,8 @@ DEPENDENCIES: - GBDeviceInfo (~> 6.3.0) - KTCenterFlowLayout (~> 1.3.1) - MatomoTracker (~> 7.2.0) - - MatrixKit (= 0.12.6) - - MatrixKit/AppExtension (= 0.12.6) + - MatrixKit (= 0.12.7) + - MatrixKit/AppExtension (= 0.12.7) - MatrixSDK/JingleCallStack - MatrixSDK/SwiftSupport - OLMKit @@ -159,8 +159,8 @@ SPEC CHECKSUMS: libbase58: 7c040313537b8c44b6e2d15586af8e21f7354efd libPhoneNumber-iOS: 0a32a9525cf8744fe02c5206eb30d571e38f7d75 MatomoTracker: 6f89e2561083685a360e223fb663e9ccd57c1d1a - MatrixKit: e4da1f13c6686134ee31769610f2aefe60bd8aa5 - MatrixSDK: efe87af67440a67781be871b07e80b1f12ef5d48 + MatrixKit: 12c75de764ebecf5dd0fab75136e731744902834 + MatrixSDK: 01b42da45d1fb28a3bceee9f3a31096bbf0a948f OLMKit: 4ee0159d63feeb86d836fdcfefe418e163511639 Realm: 4eb04d7487bd43c0581256f40b424eafb711deff Reusable: 82be188f29d96dc5eff0db7b2393bcc08d2cdd5b @@ -169,6 +169,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 6589119129fac9d854db2842066a4e7c66c880c7 +PODFILE CHECKSUM: fe376c5be11f319b515df4af2d760189ca80364b -COCOAPODS: 1.9.1 +COCOAPODS: 1.9.3 diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 6b12d6881..cea467537 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -684,6 +684,10 @@ DB1392A2332C3CAF6C9962EF /* Pods_RiotPods_RiotNSE.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4D418D054E4032F2CFA8B51 /* Pods_RiotPods_RiotNSE.framework */; }; EC2B4EF124A1EEBD005EB739 /* DataProtectionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2B4EF024A1EEBD005EB739 /* DataProtectionHelper.swift */; }; EC2B4EF224A1EF34005EB739 /* DataProtectionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2B4EF024A1EEBD005EB739 /* DataProtectionHelper.swift */; }; + EC3B066924AC6ADE000DF9BF /* CrossSigningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066424AC6ADD000DF9BF /* CrossSigningService.swift */; }; + EC3B066A24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */; }; + EC3B066B24AC6ADE000DF9BF /* CrossSigningBannerPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066724AC6ADD000DF9BF /* CrossSigningBannerPreferences.swift */; }; + EC3B066C24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066824AC6ADD000DF9BF /* CrossSigningSetupBannerCell.swift */; }; EC711B4624A63B13008F830C /* MXRecoveryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4524A63B13008F830C /* MXRecoveryService.swift */; }; EC711B7424A63B37008F830C /* SecretsSetupRecoveryKeyViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4A24A63B36008F830C /* SecretsSetupRecoveryKeyViewModelType.swift */; }; EC711B7524A63B37008F830C /* SecretsSetupRecoveryKeyCoordinatorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC711B4B24A63B36008F830C /* SecretsSetupRecoveryKeyCoordinatorType.swift */; }; @@ -1739,6 +1743,10 @@ E2599D0ECB8DD206624E450B /* Pods-RiotPods-SiriIntents.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-SiriIntents.release.xcconfig"; path = "Target Support Files/Pods-RiotPods-SiriIntents/Pods-RiotPods-SiriIntents.release.xcconfig"; sourceTree = ""; }; E4D418D054E4032F2CFA8B51 /* Pods_RiotPods_RiotNSE.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_RiotNSE.framework; sourceTree = BUILT_PRODUCTS_DIR; }; EC2B4EF024A1EEBD005EB739 /* DataProtectionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProtectionHelper.swift; sourceTree = ""; }; + EC3B066424AC6ADD000DF9BF /* CrossSigningService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningService.swift; sourceTree = ""; }; + EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CrossSigningSetupBannerCell.xib; sourceTree = ""; }; + EC3B066724AC6ADD000DF9BF /* CrossSigningBannerPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningBannerPreferences.swift; sourceTree = ""; }; + EC3B066824AC6ADD000DF9BF /* CrossSigningSetupBannerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningSetupBannerCell.swift; sourceTree = ""; }; EC711B4524A63B13008F830C /* MXRecoveryService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXRecoveryService.swift; sourceTree = ""; }; EC711B4A24A63B36008F830C /* SecretsSetupRecoveryKeyViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsSetupRecoveryKeyViewModelType.swift; sourceTree = ""; }; EC711B4B24A63B36008F830C /* SecretsSetupRecoveryKeyCoordinatorType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretsSetupRecoveryKeyCoordinatorType.swift; sourceTree = ""; }; @@ -2925,6 +2933,7 @@ B1B5567620EE6C4C00210D55 /* Modules */ = { isa = PBXGroup; children = ( + EC3B066324AC6ADD000DF9BF /* CrossSigning */, EC711BB424A63C11008F830C /* AuthenticatedSession */, EC711B9A24A63B58008F830C /* SecureBackup */, EC711B4724A63B36008F830C /* Secrets */, @@ -4315,6 +4324,25 @@ path = Recover; sourceTree = ""; }; + EC3B066324AC6ADD000DF9BF /* CrossSigning */ = { + isa = PBXGroup; + children = ( + EC3B066424AC6ADD000DF9BF /* CrossSigningService.swift */, + EC3B066524AC6ADD000DF9BF /* Banners */, + ); + path = CrossSigning; + sourceTree = ""; + }; + EC3B066524AC6ADD000DF9BF /* Banners */ = { + isa = PBXGroup; + children = ( + EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */, + EC3B066724AC6ADD000DF9BF /* CrossSigningBannerPreferences.swift */, + EC3B066824AC6ADD000DF9BF /* CrossSigningSetupBannerCell.swift */, + ); + path = Banners; + sourceTree = ""; + }; EC711B4724A63B36008F830C /* Secrets */ = { isa = PBXGroup; children = ( @@ -5022,6 +5050,7 @@ B1550FCA2420E8F500CE097B /* QRCodeReaderViewController.storyboard in Resources */, B1B5579C20EF575B00210D55 /* ForgotPasswordInputsView.xib in Resources */, F083BE011E7009ED00A9B29C /* third_party_licenses.html in Resources */, + EC3B066A24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.xib in Resources */, B1098BFC21ECFE65000DDA48 /* PasswordStrengthView.xib in Resources */, B1B5573720EE6C4D00210D55 /* GroupParticipantsViewController.xib in Resources */, B110872421F098F0003554A5 /* ActivityIndicatorView.xib in Resources */, @@ -5436,6 +5465,7 @@ B1DCC63F22E9A3AE00625807 /* EmojiItem+EmojiMart.swift in Sources */, B1DCC61C22E5E17100625807 /* EmojiPickerViewAction.swift in Sources */, B1098BDF21ECE09F000DDA48 /* Strings.swift in Sources */, + EC3B066924AC6ADE000DF9BF /* CrossSigningService.swift in Sources */, B1BEE71523DF2ACF0003A4CB /* UserVerificationCoordinator.swift in Sources */, 32607D70243E0A55006674CC /* KeyBackupRecoverFromPrivateKeyViewModel.swift in Sources */, B1B558C420EF768F00210D55 /* RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, @@ -5569,6 +5599,7 @@ B142317A22CCFA2000FFA96A /* EditHistoryCell.swift in Sources */, B1DCC62622E60CC600625807 /* EmojiItem.swift in Sources */, B1B5572A20EE6C4D00210D55 /* RoomMemberDetailsViewController.m in Sources */, + EC3B066C24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.swift in Sources */, B1B5590120EF768F00210D55 /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m in Sources */, 32F6B96B2270623100BBA352 /* KeyVerificationDataLoadingViewAction.swift in Sources */, B1B558C920EF768F00210D55 /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, @@ -5827,6 +5858,7 @@ B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */, EC711B7A24A63B37008F830C /* SecretsSetupRecoveryKeyViewAction.swift in Sources */, B12D79FB23E2462200FACEDC /* UserVerificationStartCoordinator.swift in Sources */, + EC3B066B24AC6ADE000DF9BF /* CrossSigningBannerPreferences.swift in Sources */, ECB101322477CFDB00CF8C11 /* UIDevice.swift in Sources */, B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */, EC711B8A24A63B37008F830C /* SecretsRecoveryWithPassphraseViewController.swift in Sources */, @@ -6293,7 +6325,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 0.12.7; + CURRENT_PROJECT_VERSION = 0.11.6; DEFINES_MODULE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -6313,7 +6345,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 0.12.7; + MARKETING_VERSION = 0.11.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -6351,7 +6383,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 0.12.7; + CURRENT_PROJECT_VERSION = 0.11.6; DEFINES_MODULE = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -6364,7 +6396,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 0.12.7; + MARKETING_VERSION = 0.11.6; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index f28abd3ab..efed5f883 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2298,6 +2298,9 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni // Reset key backup banner preferences [SecureBackupBannerPreferences.shared reset]; + // Reset key verification banner preferences + [CrossSigningBannerPreferences.shared reset]; + #ifdef MX_CALL_STACK_ENDPOINT // Erase all created certificates and private keys by MXEndpointCallStack for (MXKAccount *account in MXKAccountManager.sharedManager.accounts) diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 56c6f4e95..e4e92513a 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -1114,3 +1114,25 @@ "secrets_recovery_with_key_recover_action" = "Използвай ключа"; "secrets_recovery_with_key_invalid_recovery_key_title" = "Неуспешно достъпване на секретното складиране"; "secrets_recovery_with_key_invalid_recovery_key_message" = "Потвърдете, че сте въвели правилния ключ за възстановяване."; +"secure_key_backup_setup_intro_title" = "Защитено резервно копие"; +"secure_key_backup_setup_intro_info" = "Подсигурете, че няма да загубите достъп до шифрованите си съобщения и данни, като направите резервно копие на ключовете за шифроване върху сървъра."; +"secure_key_backup_setup_intro_use_security_key_title" = "Използване на защитен ключ"; +"secure_key_backup_setup_intro_use_security_key_info" = "Генерирайте защитен ключ и го съхранете на сигурно място, в password manager или сейф."; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Използване на защитна парола"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Въведете тайна парола, която знаете само вие и генерирайте ключ за резервно копие."; +"secure_key_backup_setup_cancel_alert_title" = "Сигурни ли сте?"; +"secure_key_backup_setup_cancel_alert_message" = "Ако се откажете сега, и загубите достъп до сесиите си, може да загубите шифрованите си съобщения и данни.\n\nМожете да настройте Защитено Резервно Копие и да управлявате ключовете си от Настройки."; +"secrets_setup_recovery_key_title" = "Запазване на защитния ключ"; +"secrets_setup_recovery_key_information" = "Складирайте ключа за възстановяване на сигурно място. Може да бъде използван за отключване на шифрованите съобщения и данни."; +"secrets_setup_recovery_key_loading" = "Зареждане…"; +"secrets_setup_recovery_key_export_action" = "Запази"; +"secrets_setup_recovery_key_done_action" = "Готово"; +"secrets_setup_recovery_key_storage_alert_title" = "Съхранявайте го сигурно"; +"secrets_setup_recovery_key_storage_alert_message" = "✓ Принтирайте го и го съхранете на сигурно място\n✓ Запазете го на USB или друг резервен диск\n✓ Копирайте го в личното си облачно пространство"; +"secrets_setup_recovery_passphrase_title" = "Настройка на защитна парола"; +"secrets_setup_recovery_passphrase_information" = "Въведете защитна парола, която знаете само вие, за защита на тайните върху сървъра."; +"secrets_setup_recovery_passphrase_additional_information" = "Не използвайте паролата за профила си."; +"secrets_setup_recovery_passphrase_validate_action" = "Готово"; +"secrets_setup_recovery_passphrase_confirm_information" = "Въведете защитната парола отново за да потвърдите."; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Потвърди"; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Потвърдете паролата"; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 5ea747b71..03a1ad57f 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -572,7 +572,13 @@ "security_settings_title" = "Security"; "security_settings_crypto_sessions" = "MY SESSIONS"; "security_settings_crypto_sessions_loading" = "Loading sessions…"; -"security_settings_crypto_sessions_description" = "Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup."; +"security_settings_crypto_sessions_description_2" = "If you don’t recognise a login, change your password and reset Secure Backup."; + +"security_settings_secure_backup" = "SECURE BACKUP"; +"security_settings_secure_backup_description" = "Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server."; +"security_settings_secure_backup_setup" = "Set up"; +"security_settings_secure_backup_synchronise" = "Synchronise"; +"security_settings_secure_backup_delete" = "Delete"; "security_settings_backup" = "MESSAGE BACKUP"; @@ -596,6 +602,7 @@ "security_settings_complete_security_alert_message" = "You should complete security on your current session first."; "security_settings_coming_soon" = "Sorry. This action is not available on Riot-iOS yet. Please use another Matrix client to set it up. Riot-iOS will use it."; +"security_settings_user_password_description" = "Confirm your identity by entering your account password"; // Manage session "manage_session_title" = "Manage session"; @@ -934,6 +941,12 @@ "secure_key_backup_setup_intro_use_security_passphrase_title" = "Use a Security Passphrase"; "secure_key_backup_setup_intro_use_security_passphrase_info" = "Enter a secret phrase only you know, and generate a key for backup."; +"secure_key_backup_setup_existing_backup_error_title" = "A backup for messages already exists"; +"secure_key_backup_setup_existing_backup_error_info" = "Unlock it to reuse it in the secure backup or delete it to create a new messages backup in the secure backup."; +"secure_key_backup_setup_existing_backup_error_unlock_it" = "Unlock it"; +"secure_key_backup_setup_existing_backup_error_delete_it" = "Delete it"; + + // Cancel "secure_key_backup_setup_cancel_alert_title" = "Are your sure?"; @@ -1036,7 +1049,7 @@ "sign_out_existing_key_backup_alert_sign_out_action" = "Sign out"; "sign_out_non_existing_key_backup_alert_title" = "You’ll lose access to your encrypted messages if you sign out now"; -"sign_out_non_existing_key_backup_alert_setup_key_backup_action" = "Start using Key Backup"; +"sign_out_non_existing_key_backup_alert_setup_secure_backup_action" = "Start using Secure Backup"; "sign_out_non_existing_key_backup_alert_discard_key_backup_action" = "I don't want my encrypted messages"; "sign_out_non_existing_key_backup_sign_out_confirmation_alert_title" = "You'll lose your encrypted messages"; @@ -1384,3 +1397,9 @@ "key_backup_setup_passphrase_confirm_passphrase_valid" = "Great!"; "key_backup_setup_passphrase_confirm_passphrase_invalid" = "Passphrase doesn’t match"; +// MARK: - Cross-signing + +// Banner + +"cross_signing_setup_banner_title" = "Set up encryption"; +"cross_signing_setup_banner_subtitle" = "Verify your other devices easier"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index d4a3c8815..c0774482c 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -1123,3 +1123,29 @@ "secrets_recovery_with_key_recover_action" = "Utiliser la clé"; "secrets_recovery_with_key_invalid_recovery_key_title" = "Impossible d’accéder au coffre secret"; "secrets_recovery_with_key_invalid_recovery_key_message" = "Vérifiez que vous avez saisi la bonne clé de récupération."; +"secure_key_backup_setup_intro_title" = "Sauvegarde sécurisée"; +"secure_key_backup_setup_intro_info" = "Protection afin d’éviter de perdre l’accès aux messages et données chiffrés en sauvegardant les clés de chiffrement sur votre serveur."; +"secure_key_backup_setup_intro_use_security_key_title" = "Utiliser une clé de sécurité"; +"secure_key_backup_setup_intro_use_security_key_info" = "Générer une clé de sécurité à stocker dans un endroit sûr comme un gestionnaire de mots de passe ou un coffre."; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Utiliser une phrase secrète de sécurité"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Saisissez une phrase secrète que vous êtes seul·e à connaître et générez une clé pour la sauvegarde."; +"secure_key_backup_setup_cancel_alert_title" = "En êtes-vous sûr·e ?"; +"secure_key_backup_setup_cancel_alert_message" = "Si vous annulez maintenant, vous pourriez perdre les messages et données chiffrés si vous perdez l’accès à vos connexions.\n\nVous pouvez également configurer la sauvegarde sécurisée et gérer vos clés dans les paramètres."; +"secrets_setup_recovery_key_title" = "Enregistrer votre clé de sécurité"; +"secrets_setup_recovery_key_information" = "Stockez votre clé de récupération dans un endroit sûr. Elle peut être utilisée pour déverrouiller vos messages et données chiffrés."; +"secrets_setup_recovery_key_loading" = "Chargement…"; +"secrets_setup_recovery_key_export_action" = "Sauvegarder"; +"secrets_setup_recovery_key_done_action" = "Terminé"; +"secrets_setup_recovery_key_storage_alert_title" = "Conservez-la en lieu sûr"; +"secrets_setup_recovery_key_storage_alert_message" = "✓ Imprimez-la et stockez-la dans un endroit sûr\n✓ Sauvegardez-la sur une clé USB ou un disque de sauvegarde\n✓ Copiez-la sur votre stockage personnel dans le cloud"; +"secrets_setup_recovery_passphrase_title" = "Définir une phrase de sécurité"; +"secrets_setup_recovery_passphrase_information" = "Saisissez une phrase de sécurité que vous seul·e connaissez, utilisée pour sécuriser les secrets sur votre serveur."; +"secrets_setup_recovery_passphrase_additional_information" = "N’utilisez pas le mot de passe de votre compte."; +"secrets_setup_recovery_passphrase_validate_action" = "Terminé"; +"secrets_setup_recovery_passphrase_confirm_information" = "Saisissez à nouveau votre phrase de sécurité pour la confirmer."; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Confirmer"; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Confirmer la phrase secrète"; +// AuthenticatedSessionViewControllerFactory +"authenticated_session_flow_not_supported" = "Cette application ne prend par en charge le mécanisme d’authentification de votre serveur d’accueil."; +"secure_backup_setup_banner_title" = "Sauvegarde sécurisée"; +"secure_backup_setup_banner_subtitle" = "Protection afin d’éviter de perdre l’accès aux messages et données chiffrés"; diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index f09045b3a..184350aca 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -1097,3 +1097,29 @@ "secrets_recovery_with_key_recover_action" = "Usa chiave"; "secrets_recovery_with_key_invalid_recovery_key_title" = "Impossibile accedere all'archivio segreto"; "secrets_recovery_with_key_invalid_recovery_key_message" = "Controlla di avere inserito la chiave di recupero corretta."; +"secure_key_backup_setup_intro_title" = "Backup sicuro"; +"secure_key_backup_setup_intro_info" = "Salvaguardia contro la perdita dell'accesso ai messaggi e dati cifrati facendo un backup delle chiavi crittografiche sul tuo server."; +"secure_key_backup_setup_intro_use_security_key_title" = "Usa una chiave di sicurezza"; +"secure_key_backup_setup_intro_use_security_key_info" = "Genera una chiave di sicurezza da conservare in qualche posto sicuro, come un gestore di password o una cassaforte."; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Usa una frase di sicurezza"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Inserisci una frase segreta che conosci solo tu e genera una chiave per backup."; +"secure_key_backup_setup_cancel_alert_title" = "Sei sicuro?"; +"secure_key_backup_setup_cancel_alert_message" = "Se annulli ora, potresti perdere i messaggi e dati cifrati se perdi l'accesso ai tuoi login.\n\nPuoi anche configurare il backup sicuro e gestire le tue chiavi nelle impostazioni."; +"secrets_setup_recovery_key_title" = "Salva la chiave di sicurezza"; +"secrets_setup_recovery_key_information" = "Conserva la chiave di sicurezza in un posto sicuro. Può essere usata per sbloccare i tuoi messaggi e dati cifrati."; +"secrets_setup_recovery_key_loading" = "Caricamento…"; +"secrets_setup_recovery_key_export_action" = "Salva"; +"secrets_setup_recovery_key_done_action" = "Fatto"; +"secrets_setup_recovery_key_storage_alert_title" = "Tienila al sicuro"; +"secrets_setup_recovery_key_storage_alert_message" = "✓ Stampala e conservala in un posto sicuro\n✓ Salvala in una chiave USB o disco di backup\n✓ Copiala nel tuo archivio cloud pesonale"; +"secrets_setup_recovery_passphrase_title" = "Imposta una frase di sicurezza"; +"secrets_setup_recovery_passphrase_information" = "Inserisci una frase di sicurezza che conosci solo tu, usata per proteggere i segreti nel tuo server."; +"secrets_setup_recovery_passphrase_additional_information" = "Non usare la password del tuo account."; +"secrets_setup_recovery_passphrase_validate_action" = "Fatto"; +"secrets_setup_recovery_passphrase_confirm_information" = "Inserisci la frase di sicurezza di nuovo per confermarla."; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Conferma"; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Conferma frase di sicurezza"; +// AuthenticatedSessionViewControllerFactory +"authenticated_session_flow_not_supported" = "Questa app non supporta il metodo di autenticazione del tuo homeserver."; +"secure_backup_setup_banner_title" = "Backup Sicuro"; +"secure_backup_setup_banner_subtitle" = "Proteggiti dalla perdita dei messaggi e dati crittografati"; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index 4ae1ce464..de09b3ccd 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -229,7 +229,7 @@ "room_event_action_view_encryption" = "Versleutelingsinformatie"; "room_warning_about_encryption" = "Eind-tot-eindversleuteling is in bèta en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen de geschiedenis van vóór ze het gesprek betraden nog niet ontsleutelen.\n\nVersleutelde berichten zullen niet zichtbaar zijn op cliënten die nog geen versleuteling ondersteunen."; // Unknown devices -"unknown_devices_alert_title" = "Gesprek bevat onbekende apparaten"; +"unknown_devices_alert_title" = "Gesprek bevat onbekende sessies"; "unknown_devices_alert" = "Dit gesprek bevat onbekende apparaten die niet geverifieerd zijn.\nDit betekent dat er geen garantie is dat de apparaten bij de gebruikers horen waarbij ze beweren te horen.\nWe raden u aan om bij elk apparaat door het verificatieproces heen te gaan voordat u verdergaat, maar u kunt het bericht ook zonder verificatie opnieuw versturen."; "unknown_devices_send_anyway" = "Alsnog versturen"; "unknown_devices_call_anyway" = "Alsnog bellen"; @@ -855,3 +855,17 @@ "media_type_accessibility_sticker" = "Sticker"; // Widget Picker "widget_picker_title" = "Integraties"; +"skip" = "Overslaan"; +// Accessibility +"accessibility_checkbox_label" = "aanvinkvak"; +// MARK: Clients +"client_desktop_name" = "Riot Desktop"; +"client_web_name" = "Riot Web"; +"client_ios_name" = "Riot iOS"; +"client_android_name" = "Riot X voor Android"; +"auth_add_email_message_2" = "Stel een e-mailadres in voor accountherstel en om later optioneel vindbaar te zijn voor mensen die u kennen."; +"auth_add_phone_message_2" = "Stel een telefoon in om later optioneel vindbaar te zijn voor mensen die u kennen."; +"auth_add_email_phone_message_2" = "Stel een e-mailadres in voor accountherstel. Gebruik later e-mail of telefoon om optioneel vindbaar te zijn voor mensen die u kennen."; +"auth_email_is_required" = "Er is geen identiteitsserver geconfigureerd, dus u kunt geen e-mailadres toevoegen om uw wachtwoord in de toekomst opnieuw in te stellen."; +"auth_phone_is_required" = "Er is geen identiteitsserver geconfigureerd, dus u kunt geen telefoonnummer toevoegen om uw wachtwoord in de toekomst opnieuw in te stellen."; +"auth_reset_password_error_is_required" = "Geen identiteitsserver ingesteld: voeg er één toe bij server opties om uw wachtwoord te wijzigen."; diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index f61a3babe..6e5c0fdb7 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -1105,3 +1105,35 @@ "secrets_recovery_with_passphrase_invalid_passphrase_title" = "S’arrihet të hyhet në depo të fshehtë"; "secrets_recovery_with_passphrase_invalid_passphrase_message" = "Ju lutemi, verifikoni që keni dhënë frazëkalimin e saktë të rimarrjeve."; "secrets_recovery_with_key_title" = "Kyç Rimarrjesh"; +"secrets_recovery_with_key_information_default" = "Hyni te historiku i mesazheve tuaj të sigurt dhe te identiteti juaj për cross-signing për verifikim sesionesh të tjerë përmes dhënies së kyçit të rimarrjeve."; +"secrets_recovery_with_key_information_verify_device" = "Që të verifikoni këtë pajisje, përdorni Kyç Rimarrjesh."; +"secrets_recovery_with_key_recovery_key_placeholder" = "Jepni Kyç Rimarrjesh"; +"secrets_recovery_with_key_recover_action" = "Përdor Kyç"; +"secrets_recovery_with_key_invalid_recovery_key_title" = "S’arrihet të hyhet në depozitë të fshehtë"; +"secrets_recovery_with_key_invalid_recovery_key_message" = "Ju lutemi, verifikoni se dhatë kyçin e saktë të rimarrjeve."; +// AuthenticatedSessionViewControllerFactory +"authenticated_session_flow_not_supported" = "Ky aplikacion nuk mbulon mekanizimin e mirëfilltësimeve në shërbyesin tuaj Home."; +"secure_key_backup_setup_intro_title" = "Kopjeruajtje e Sigurt"; +"secure_key_backup_setup_intro_info" = "Mbrohuni kundër humbjes së hyrjes në mesazhe & të dhëna të fshehtëzuara, duke kopjeruajtur kyçe fshehtëzimi në shërbyesin tuaj."; +"secure_key_backup_setup_intro_use_security_key_title" = "Përdorni një Kyç Sigurie"; +"secure_key_backup_setup_intro_use_security_key_info" = "Prodhoni një kyç sigurie për ta ruajtur diku të parrezik, bie fjala, në një përgjegjës fjalëkalimesh apo në një kasafortë."; +"secure_key_backup_setup_intro_use_security_passphrase_title" = "Përdorni një Frazëkalim Sigurie"; +"secure_key_backup_setup_intro_use_security_passphrase_info" = "Jepni një frazë të fshehtë që e dini vetëm ju, dhe prodhoni një kyç për kopjeruajtje."; +"secure_key_backup_setup_cancel_alert_title" = "Jeni i sigurt?"; +"secure_key_backup_setup_cancel_alert_message" = "Nëse e anuloni tani, mund të humbni mesazhe & të dhëna të fshehtëzuara, nëse humbni hyrje te kredenciale tuajt hyrjesh.\n\nMundeni edhe të ujdisni Kopjeruajtje të Sigurt & të administroni kyçet tuaj që nga Rregullimet."; +"secure_backup_setup_banner_title" = "Kopjeruajtje e Sigurt"; +"secure_backup_setup_banner_subtitle" = "Mbrohuni kundër humbjes së hyrjes te mesazhe & të dhëna të fshehtëzuara"; +"secrets_setup_recovery_key_title" = "Ruani Kyçin tuaj të Sigurisë"; +"secrets_setup_recovery_key_information" = "Mbajeni Kyçin tuaj të Rimarrjeve diku të parrezik. Mund të përdoret për të shkyçur mesazhet & të dhënat tuaja të fshehtëzuara."; +"secrets_setup_recovery_key_loading" = "Po ngarkohet…"; +"secrets_setup_recovery_key_export_action" = "Ruaje"; +"secrets_setup_recovery_key_done_action" = "U bë"; +"secrets_setup_recovery_key_storage_alert_title" = "Ruajeni të parrezik"; +"secrets_setup_recovery_key_storage_alert_message" = "✓ Shtypeni dhe vendoseni diku të parrezik\n✓ Ruajeni në një diskth USB ose në një pajisje kopjeruajtjesh\n✓ Kopjojeni te depoja juaj personale në re"; +"secrets_setup_recovery_passphrase_title" = "Caktoni një Frazë Sigurie"; +"secrets_setup_recovery_passphrase_information" = "Jepni një frazë sigurie që e dini vetëm ju, për ta përdorur për sigurim të fshehtash në shërbyesin tuaj."; +"secrets_setup_recovery_passphrase_additional_information" = "Mos përdorni fjalëkalimin e llogarisë tuaj."; +"secrets_setup_recovery_passphrase_validate_action" = "U bë"; +"secrets_setup_recovery_passphrase_confirm_information" = "Që ta ripohoni, rijepeni Frazën e Sigurisë."; +"secrets_setup_recovery_passphrase_confirm_passphrase_title" = "Ripohojeni"; +"secrets_setup_recovery_passphrase_confirm_passphrase_placeholder" = "Ripohoni frazëkalimin"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 5ebdbfd12..8ef473a0b 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -482,6 +482,14 @@ internal enum VectorL10n { internal static var create: String { return VectorL10n.tr("Vector", "create") } + /// Verify your other devices easier + internal static var crossSigningSetupBannerSubtitle: String { + return VectorL10n.tr("Vector", "cross_signing_setup_banner_subtitle") + } + /// Set up encryption + internal static var crossSigningSetupBannerTitle: String { + return VectorL10n.tr("Vector", "cross_signing_setup_banner_title") + } /// Please forget all messages I have sent when my account is deactivated ( internal static var deactivateAccountForgetMessagesInformationPart1: String { return VectorL10n.tr("Vector", "deactivate_account_forget_messages_information_part1") @@ -3062,6 +3070,22 @@ internal enum VectorL10n { internal static var secureKeyBackupSetupCancelAlertTitle: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_cancel_alert_title") } + /// Delete it + internal static var secureKeyBackupSetupExistingBackupErrorDeleteIt: String { + return VectorL10n.tr("Vector", "secure_key_backup_setup_existing_backup_error_delete_it") + } + /// Unlock it to reuse it in the secure backup or delete it to create a new messages backup in the secure backup. + internal static var secureKeyBackupSetupExistingBackupErrorInfo: String { + return VectorL10n.tr("Vector", "secure_key_backup_setup_existing_backup_error_info") + } + /// A backup for messages already exists + internal static var secureKeyBackupSetupExistingBackupErrorTitle: String { + return VectorL10n.tr("Vector", "secure_key_backup_setup_existing_backup_error_title") + } + /// Unlock it + internal static var secureKeyBackupSetupExistingBackupErrorUnlockIt: String { + return VectorL10n.tr("Vector", "secure_key_backup_setup_existing_backup_error_unlock_it") + } /// Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server. internal static var secureKeyBackupSetupIntroInfo: String { return VectorL10n.tr("Vector", "secure_key_backup_setup_intro_info") @@ -3150,9 +3174,9 @@ internal enum VectorL10n { internal static var securitySettingsCryptoSessions: String { return VectorL10n.tr("Vector", "security_settings_crypto_sessions") } - /// Trust sessions to grant access to end-to-end encrypted messages. If you don’t recognise a session, change your login password and reset your Message Password used for Message Backup. - internal static var securitySettingsCryptoSessionsDescription: String { - return VectorL10n.tr("Vector", "security_settings_crypto_sessions_description") + /// If you don’t recognise a login, change your password and reset Secure Backup. + internal static var securitySettingsCryptoSessionsDescription2: String { + return VectorL10n.tr("Vector", "security_settings_crypto_sessions_description_2") } /// Loading sessions… internal static var securitySettingsCryptoSessionsLoading: String { @@ -3166,10 +3190,34 @@ internal enum VectorL10n { internal static var securitySettingsExportKeysManually: String { return VectorL10n.tr("Vector", "security_settings_export_keys_manually") } + /// SECURE BACKUP + internal static var securitySettingsSecureBackup: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup") + } + /// Delete + internal static var securitySettingsSecureBackupDelete: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_delete") + } + /// Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server. + internal static var securitySettingsSecureBackupDescription: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_description") + } + /// Set up + internal static var securitySettingsSecureBackupSetup: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_setup") + } + /// Synchronise + internal static var securitySettingsSecureBackupSynchronise: String { + return VectorL10n.tr("Vector", "security_settings_secure_backup_synchronise") + } /// Security internal static var securitySettingsTitle: String { return VectorL10n.tr("Vector", "security_settings_title") } + /// Confirm your identity by entering your account password + internal static var securitySettingsUserPasswordDescription: String { + return VectorL10n.tr("Vector", "security_settings_user_password_description") + } /// Send to %@ internal static func sendTo(_ p1: String) -> String { return VectorL10n.tr("Vector", "send_to", p1) @@ -3818,9 +3866,9 @@ internal enum VectorL10n { internal static var signOutNonExistingKeyBackupAlertDiscardKeyBackupAction: String { return VectorL10n.tr("Vector", "sign_out_non_existing_key_backup_alert_discard_key_backup_action") } - /// Start using Key Backup - internal static var signOutNonExistingKeyBackupAlertSetupKeyBackupAction: String { - return VectorL10n.tr("Vector", "sign_out_non_existing_key_backup_alert_setup_key_backup_action") + /// Start using Secure Backup + internal static var signOutNonExistingKeyBackupAlertSetupSecureBackupAction: String { + return VectorL10n.tr("Vector", "sign_out_non_existing_key_backup_alert_setup_secure_backup_action") } /// You’ll lose access to your encrypted messages if you sign out now internal static var signOutNonExistingKeyBackupAlertTitle: String { diff --git a/Riot/Modules/AuthenticatedSession/AuthenticatedSessionViewControllerFactory.swift b/Riot/Modules/AuthenticatedSession/AuthenticatedSessionViewControllerFactory.swift index 56b4dc4fe..a62f57cd9 100644 --- a/Riot/Modules/AuthenticatedSession/AuthenticatedSessionViewControllerFactory.swift +++ b/Riot/Modules/AuthenticatedSession/AuthenticatedSessionViewControllerFactory.swift @@ -70,6 +70,7 @@ final class AuthenticatedSessionViewControllerFactory: NSObject { /// - onAuthenticated: the block called when the user finished to enter their credentials. /// - onCancelled: the block called when the user cancelled the authentication. /// - onFailure: the blocked called on error. + @discardableResult func viewController(forPath path: String, httpMethod: String, title: String?, diff --git a/Riot/Modules/Authentication/AuthenticationViewController.m b/Riot/Modules/Authentication/AuthenticationViewController.m index d7757b16f..05b0c7a27 100644 --- a/Riot/Modules/Authentication/AuthenticationViewController.m +++ b/Riot/Modules/Authentication/AuthenticationViewController.m @@ -1154,14 +1154,10 @@ { case MXCrossSigningStateNotBootstrapped: { -#ifdef NEW_CROSS_SIGNING_FLOW // TODO: This is still not sure we want to disable the automatic cross-signing bootstrap // if the admin disabled e2e by default. // Do like riot-web for the moment if (session.vc_isE2EByDefaultEnabledByHSAdmin) -#else - if (0) -#endif { // Bootstrap cross-signing on user's account // We do it for both registration and new login as long as cross-signing does not exist yet diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h index 5304c62b3..92c74d8cb 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.h @@ -41,6 +41,15 @@ typedef NS_ENUM(NSInteger, SecureBackupBannerDisplay) SecureBackupBannerDisplaySetup }; +/** + List the different cross-signing banners that could be displayed. + */ +typedef NS_ENUM(NSInteger, CrossSigningBannerDisplay) +{ + CrossSigningBannerDisplayNone, + CrossSigningBannerDisplaySetup +}; + /** Action identifier used when the user tapped on the directory change button. @@ -54,6 +63,7 @@ extern NSString *const kRecentsDataSourceTapOnDirectoryServerChange; */ @interface RecentsDataSource : MXKInterleavedRecentsDataSource +@property (nonatomic) NSInteger crossSigningBannerSection; @property (nonatomic) NSInteger secureBackupBannerSection; @property (nonatomic) NSInteger directorySection; @property (nonatomic) NSInteger invitesSection; @@ -71,6 +81,7 @@ extern NSString *const kRecentsDataSourceTapOnDirectoryServerChange; @property (nonatomic, readonly) NSArray* serverNoticeCellDataArray; @property (nonatomic, readonly) SecureBackupBannerDisplay secureBackupBannerDisplay; +@property (nonatomic, readonly) CrossSigningBannerDisplay crossSigningBannerDisplay; /** Set the delegate by specifying the selected display mode. diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m index a31fc3869..894b5f96c 100644 --- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m +++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m @@ -43,7 +43,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSourceTapOnDirectoryServerChange"; -@interface RecentsDataSource() +@interface RecentsDataSource() { NSMutableArray* invitesCellDataArray; NSMutableArray* favoriteCellDataArray; @@ -65,11 +65,14 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } @property (nonatomic, assign, readwrite) SecureBackupBannerDisplay secureBackupBannerDisplay; +@property (nonatomic, assign, readwrite) CrossSigningBannerDisplay crossSigningBannerDisplay; + +@property (nonatomic, strong) CrossSigningService *crossSigningService; @end @implementation RecentsDataSource -@synthesize directorySection, invitesSection, favoritesSection, peopleSection, conversationSection, lowPrioritySection, serverNoticeSection, secureBackupBannerSection; +@synthesize directorySection, invitesSection, favoritesSection, peopleSection, conversationSection, lowPrioritySection, serverNoticeSection, secureBackupBannerSection, crossSigningBannerSection; @synthesize hiddenCellIndexPath, droppingCellIndexPath, droppingCellBackGroundView; @synthesize invitesCellDataArray, favoriteCellDataArray, peopleCellDataArray, conversationCellDataArray, lowPriorityCellDataArray, serverNoticeCellDataArray; @@ -84,7 +87,9 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou lowPriorityCellDataArray = [[NSMutableArray alloc] init]; serverNoticeCellDataArray = [[NSMutableArray alloc] init]; conversationCellDataArray = [[NSMutableArray alloc] init]; - + + _crossSigningBannerDisplay = CrossSigningBannerDisplayNone; + crossSigningBannerSection = -1; _secureBackupBannerDisplay = SecureBackupBannerDisplayNone; secureBackupBannerSection = -1; @@ -101,6 +106,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou roomTagsListenerByUserId = [[NSMutableDictionary alloc] init]; + _crossSigningService = [CrossSigningService new]; + // Set default data and view classes [self registerCellDataClass:RecentCellData.class forCellIdentifier:kMXKRecentCellIdentifier]; } @@ -132,6 +139,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou [self updateSecureBackupBanner]; [self forceRefresh]; + [self refreshCrossSigningBannerDisplay]; } - (UIView *)viewForStickyHeaderInSection:(NSInteger)section withFrame:(CGRect)frame @@ -212,6 +220,64 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou [self forceRefresh]; } +#pragma mark - Cross-signing setup banner + +- (void)refreshCrossSigningBannerDisplay +{ + if (self.recentsDataSourceMode == RecentsDataSourceModeHome) + { + CrossSigningBannerPreferences *crossSigningBannerPreferences = CrossSigningBannerPreferences.shared; + + if (!crossSigningBannerPreferences.hideSetupBanner) + { + [self.crossSigningService canSetupCrossSigningFor:self.mxSession success:^(BOOL canSetupCrossSigning) { + + CrossSigningBannerDisplay crossSigningBannerDisplay = canSetupCrossSigning ? CrossSigningBannerDisplaySetup : CrossSigningBannerDisplayNone; + + [self updateCrossSigningBannerDisplay:crossSigningBannerDisplay]; + + } failure:^(NSError * _Nonnull error) { + NSLog(@"[RecentsDataSource] refreshCrossSigningBannerDisplay: Fail to verify if cross signing banner can be displayed"); + }]; + } + else + { + [self updateCrossSigningBannerDisplay:CrossSigningBannerDisplayNone]; + } + } + else + { + [self updateCrossSigningBannerDisplay:CrossSigningBannerDisplayNone]; + } +} + +- (void)updateCrossSigningBannerDisplay:(CrossSigningBannerDisplay)crossSigningBannerDisplay +{ + if (self.crossSigningBannerDisplay == crossSigningBannerDisplay) + { + return; + } + + self.crossSigningBannerDisplay = crossSigningBannerDisplay; + [self forceRefresh]; +} + + +- (void)hideCrossSigningBannerWithDisplay:(CrossSigningBannerDisplay)crossSigningBannerDisplay +{ + CrossSigningBannerPreferences *crossSigningBannerPreferences = CrossSigningBannerPreferences.shared; + + switch (crossSigningBannerDisplay) { + case CrossSigningBannerDisplaySetup: + crossSigningBannerPreferences.hideSetupBanner = YES; + break; + default: + break; + } + + [self refreshCrossSigningBannerDisplay]; +} + #pragma mark - - (MXKSessionRecentsDataSource *)addMatrixSession:(MXSession *)mxSession @@ -326,11 +392,15 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou // Check whether all data sources are ready before rendering recents if (self.state == MXKDataSourceStateReady) { - secureBackupBannerSection = directorySection = favoritesSection = peopleSection = conversationSection = lowPrioritySection = invitesSection = serverNoticeSection = -1; + crossSigningBannerSection = secureBackupBannerSection = directorySection = favoritesSection = peopleSection = conversationSection = lowPrioritySection = invitesSection = serverNoticeSection = -1; - if (self.secureBackupBannerDisplay != SecureBackupBannerDisplayNone) + if (self.crossSigningBannerDisplay != CrossSigningBannerDisplayNone) { - self.secureBackupBannerSection = sectionsCount++; + crossSigningBannerSection = sectionsCount++; + } + else if (self.secureBackupBannerDisplay != SecureBackupBannerDisplayNone) + { + secureBackupBannerSection = sectionsCount++; } if (invitesCellDataArray.count > 0) @@ -385,7 +455,11 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou NSUInteger count = 0; - if (section == self.secureBackupBannerSection && self.secureBackupBannerDisplay != SecureBackupBannerDisplayNone) + if (section == self.crossSigningBannerSection && self.crossSigningBannerDisplay != CrossSigningBannerDisplayNone) + { + count = 1; + } + else if (section == self.secureBackupBannerSection && self.secureBackupBannerDisplay != SecureBackupBannerDisplayNone) { count = 1; } @@ -434,7 +508,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou - (CGFloat)heightForHeaderInSection:(NSInteger)section { - if (section == self.secureBackupBannerSection) + if (section == self.secureBackupBannerSection || section == self.crossSigningBannerSection) { return 0.0; } @@ -598,7 +672,7 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou - (UIView *)viewForHeaderInSection:(NSInteger)section withFrame:(CGRect)frame { // No header view in key backup banner section - if (section == self.secureBackupBannerSection) + if (section == self.secureBackupBannerSection || section == self.crossSigningBannerSection) { return nil; } @@ -746,7 +820,13 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou return [[UITableViewCell alloc] init]; } - if (indexPath.section == self.secureBackupBannerSection) + if (indexPath.section == self.crossSigningBannerSection) + { + CrossSigningSetupBannerCell* crossSigningSetupBannerCell = [tableView dequeueReusableCellWithIdentifier:CrossSigningSetupBannerCell.defaultReuseIdentifier forIndexPath:indexPath]; + crossSigningSetupBannerCell.delegate = self; + return crossSigningSetupBannerCell; + } + else if (indexPath.section == self.secureBackupBannerSection) { SecureBackupBannerCell* keyBackupBannerCell = [tableView dequeueReusableCellWithIdentifier:SecureBackupBannerCell.defaultReuseIdentifier forIndexPath:indexPath]; [keyBackupBannerCell configureFor:self.secureBackupBannerDisplay]; @@ -1542,11 +1622,18 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou } } -#pragma mark - secureBackupSetupBannerCellDelegate +#pragma mark - SecureBackupSetupBannerCellDelegate - (void)secureBackupBannerCellDidTapCloseAction:(SecureBackupBannerCell * _Nonnull)cell { [self hideKeyBackupBannerWithDisplay:self.secureBackupBannerDisplay]; } +#pragma mark - CrossSigningSetupBannerCellDelegate + +- (void)crossSigningSetupBannerCellDidTapCloseAction:(CrossSigningSetupBannerCell *)cell +{ + [self hideCrossSigningBannerWithDisplay:self.crossSigningBannerDisplay]; +} + @end diff --git a/Riot/Modules/CrossSigning/Banners/CrossSigningBannerPreferences.swift b/Riot/Modules/CrossSigning/Banners/CrossSigningBannerPreferences.swift new file mode 100644 index 000000000..56565a81c --- /dev/null +++ b/Riot/Modules/CrossSigning/Banners/CrossSigningBannerPreferences.swift @@ -0,0 +1,49 @@ +/* + Copyright 2020 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 Foundation + +/// Cross-Signing banner user preferences. +@objcMembers +final class CrossSigningBannerPreferences: NSObject { + + // MARK: - Constants + + private enum UserDefaultsKeys { + static let hideSetupBanner = "CrossSigningBannerPreferencesHideSetupBanner" + } + + static let shared = CrossSigningBannerPreferences() + + // MARK: - Properties + + // MARK: - Public + + /// Remember to hide key backup setup banner. + var hideSetupBanner: Bool { + get { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.hideSetupBanner) + } + set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.hideSetupBanner) + } + } + + /// Reset key backup banner preferences to default values + func reset() { + self.hideSetupBanner = false + } +} diff --git a/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.swift b/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.swift new file mode 100644 index 000000000..6a7161bea --- /dev/null +++ b/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.swift @@ -0,0 +1,87 @@ +/* + Copyright 2020 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 UIKit + +@objc protocol CrossSigningSetupBannerCellDelegate: class { + func crossSigningSetupBannerCellDidTapCloseAction(_ cell: CrossSigningSetupBannerCell) +} + +@objcMembers +final class CrossSigningSetupBannerCell: MXKTableViewCell, Themable { + + // MARK: - Properties + + // MARK: Outlets + + @IBOutlet private weak var shieldImageView: UIImageView! + @IBOutlet private weak var titleLabel: UILabel! + @IBOutlet private weak var subtitleLabel: UILabel! + @IBOutlet private weak var closeButton: UIButton! + + // MARK: Public + + weak var delegate: CrossSigningSetupBannerCellDelegate? + + // MARK: - Overrides + + override class func defaultReuseIdentifier() -> String { + return String(describing: self) + } + + override class func nib() -> UINib { + return UINib(nibName: String(describing: self), bundle: nil) + } + + override func customizeRendering() { + super.customizeRendering() + + let theme = ThemeService.shared().theme + self.update(theme: theme) + } + + // MARK: - Life cycle + + override func awakeFromNib() { + super.awakeFromNib() + + // TODO: Image size is too small, use an higher resolution one. + let shieldImage = Asset.Images.encryptionNormal.image.withRenderingMode(.alwaysTemplate) + self.shieldImageView.image = shieldImage + + let closeImage = Asset.Images.closeBanner.image.withRenderingMode(.alwaysTemplate) + self.closeButton.setImage(closeImage, for: .normal) + + self.titleLabel.text = VectorL10n.crossSigningSetupBannerTitle + self.subtitleLabel.text = VectorL10n.crossSigningSetupBannerSubtitle + } + + // MARK: - Public + + func update(theme: Theme) { + self.shieldImageView.tintColor = theme.textPrimaryColor + self.closeButton.tintColor = theme.textPrimaryColor + + self.titleLabel.textColor = theme.textPrimaryColor + self.subtitleLabel.textColor = theme.textPrimaryColor + } + + // MARK: - Actions + + @IBAction private func closeButtonAction(_ sender: Any) { + self.delegate?.crossSigningSetupBannerCellDidTapCloseAction(self) + } +} diff --git a/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.xib b/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.xib new file mode 100644 index 000000000..0d66e087b --- /dev/null +++ b/Riot/Modules/CrossSigning/Banners/CrossSigningSetupBannerCell.xib @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/Modules/CrossSigning/CrossSigningService.swift b/Riot/Modules/CrossSigning/CrossSigningService.swift new file mode 100644 index 000000000..1168a8243 --- /dev/null +++ b/Riot/Modules/CrossSigning/CrossSigningService.swift @@ -0,0 +1,63 @@ +/* + Copyright 2020 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 Foundation + +@objcMembers +final class CrossSigningService: NSObject { + + private var authenticatedSessionFactory: AuthenticatedSessionViewControllerFactory? + private var supportSetupKeyVerificationByUser: [String: Bool] = [:] // Cached server response + + @discardableResult + func canSetupCrossSigning(for session: MXSession, success: @escaping ((Bool) -> Void), failure: @escaping ((Error) -> Void)) -> MXHTTPOperation? { + + guard let crossSigning = session.crypto?.crossSigning, crossSigning.state == .notBootstrapped else { + // Cross-signing already setup + success(false) + return nil + } + + let userId: String = session.myUserId + + if let supportSetupKeyVerification = self.supportSetupKeyVerificationByUser[userId] { + // Return cached response + success(supportSetupKeyVerification) + return nil + } + + let authenticatedSessionFactory = AuthenticatedSessionViewControllerFactory(session: session) + + self.authenticatedSessionFactory = authenticatedSessionFactory + + let path = "\(kMXAPIPrefixPathUnstable)/keys/device_signing/upload" + + return authenticatedSessionFactory.hasSupport(forPath: path, httpMethod: "POST", success: { [weak self] succeeded in + guard let self = self else { + return + } + self.authenticatedSessionFactory = nil + self.supportSetupKeyVerificationByUser[userId] = succeeded + success(succeeded) + }, failure: { [weak self] error in + guard let self = self else { + return + } + self.authenticatedSessionFactory = nil + failure(error) + }) + } +} diff --git a/Riot/Modules/Home/HomeViewController.m b/Riot/Modules/Home/HomeViewController.m index 5cd7c8d5d..4627b0be3 100644 --- a/Riot/Modules/Home/HomeViewController.m +++ b/Riot/Modules/Home/HomeViewController.m @@ -44,6 +44,9 @@ @property (nonatomic, strong) SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter; @property (nonatomic, strong) SecureBackupBannerCell *secureBackupBannerPrototypeCell; +@property (nonatomic, strong) CrossSigningSetupBannerCell *keyVerificationSetupBannerPrototypeCell; +@property (nonatomic, strong) AuthenticatedSessionViewControllerFactory *authenticatedSessionViewControllerFactory; + @end @implementation HomeViewController @@ -79,6 +82,9 @@ // Register key backup banner cells [self.recentsTableView registerNib:SecureBackupBannerCell.nib forCellReuseIdentifier:SecureBackupBannerCell.defaultReuseIdentifier]; + // Register key verification banner cells + [self.recentsTableView registerNib:CrossSigningSetupBannerCell.nib forCellReuseIdentifier:CrossSigningSetupBannerCell.defaultReuseIdentifier]; + // Change the table data source. It must be the home view controller itself. self.recentsTableView.dataSource = self; } @@ -153,6 +159,15 @@ return _secureBackupBannerPrototypeCell; } +- (CrossSigningSetupBannerCell *)keyVerificationSetupBannerPrototypeCell +{ + if (!_keyVerificationSetupBannerPrototypeCell) + { + _keyVerificationSetupBannerPrototypeCell = [self.recentsTableView dequeueReusableCellWithIdentifier:CrossSigningSetupBannerCell.defaultReuseIdentifier]; + } + return _keyVerificationSetupBannerPrototypeCell; +} + - (void)presentSecureBackupSetup { SecureBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; @@ -289,7 +304,9 @@ { if ((indexPath.section == recentsDataSource.conversationSection && !recentsDataSource.conversationCellDataArray.count) || (indexPath.section == recentsDataSource.peopleSection && !recentsDataSource.peopleCellDataArray.count) - || (indexPath.section == recentsDataSource.secureBackupBannerSection)) + || (indexPath.section == recentsDataSource.secureBackupBannerSection) + || (indexPath.section == recentsDataSource.crossSigningBannerSection) + ) { return [recentsDataSource tableView:tableView cellForRowAtIndexPath:indexPath]; } @@ -365,12 +382,22 @@ { return [recentsDataSource cellHeightAtIndexPath:indexPath]; } - else if (indexPath.section == recentsDataSource.secureBackupBannerSection) + else if (indexPath.section == recentsDataSource.secureBackupBannerSection || indexPath.section == recentsDataSource.crossSigningBannerSection) { CGFloat height = 0.0; - SecureBackupBannerCell *sizingCell = self.secureBackupBannerPrototypeCell; - [sizingCell configureFor:recentsDataSource.secureBackupBannerDisplay]; + UITableViewCell *sizingCell; + + if (indexPath.section == recentsDataSource.secureBackupBannerSection) + { + SecureBackupBannerCell *secureBackupBannerCell = self.secureBackupBannerPrototypeCell; + [secureBackupBannerCell configureFor:recentsDataSource.secureBackupBannerDisplay]; + sizingCell = secureBackupBannerCell; + } + else if (indexPath.section == recentsDataSource.crossSigningBannerSection) + { + sizingCell = self.keyVerificationSetupBannerPrototypeCell; + } [sizingCell layoutIfNeeded]; @@ -408,7 +435,8 @@ - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { // No header in key banner section - if (section == recentsDataSource.secureBackupBannerSection) + if (section == recentsDataSource.secureBackupBannerSection + || section == recentsDataSource.crossSigningBannerSection) { return 0.0; } @@ -430,6 +458,10 @@ break; } } + else if (indexPath.section == recentsDataSource.crossSigningBannerSection) + { + [self showCrossSigningSetup]; + } } #pragma mark - UICollectionViewDataSource @@ -691,4 +723,83 @@ self.secureBackupSetupCoordinatorBridgePresenter = nil; } +#pragma mark - Cross-signing setup + +- (void)showCrossSigningSetup +{ + [self setupCrossSigningWithTitle:NSLocalizedStringFromTable(@"cross_signing_setup_banner_title", @"Vector", nil) message:NSLocalizedStringFromTable(@"security_settings_user_password_description", @"Vector", nil) success:^{ + + } failure:^(NSError *error) { + + }]; +} + +- (void)setupCrossSigningWithTitle:(NSString*)title + message:(NSString*)message + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + __block UIViewController *viewController; + [self startActivityIndicator]; + self.view.userInteractionEnabled = NO; + + void (^animationCompletion)(void) = ^void () { + [self stopActivityIndicator]; + self.view.userInteractionEnabled = YES; + }; + + // Get credentials to set up cross-signing + NSString *path = [NSString stringWithFormat:@"%@/keys/device_signing/upload", kMXAPIPrefixPathUnstable]; + self.authenticatedSessionViewControllerFactory = [[AuthenticatedSessionViewControllerFactory alloc] initWithSession:self.mainSession]; + [self.authenticatedSessionViewControllerFactory viewControllerForPath:path + httpMethod:@"POST" + title:title + message:message + onViewController:^(UIViewController * _Nonnull theViewController) + { + viewController = theViewController; + [self presentViewController:viewController animated:YES completion:nil]; + + } onAuthenticated:^(NSDictionary * _Nonnull authParams) { + + [viewController dismissViewControllerAnimated:NO completion:nil]; + viewController = nil; + + MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; + if (crossSigning) + { + [crossSigning setupWithAuthParams:authParams success:^{ + animationCompletion(); + + // TODO: Remove this line and refresh key verification setup banner by listening to a local notification cross-signing state change (Add this behavior into the SDK). + [self->recentsDataSource setDelegate:self andRecentsDataSourceMode:RecentsDataSourceModeHome]; + + [self refreshRecentsTable]; + success(); + } failure:^(NSError * _Nonnull error) { + animationCompletion(); + [self refreshRecentsTable]; + + [[AppDelegate theDelegate] showErrorAsAlert:error]; + failure(error); + }]; + } + + } onCancelled:^{ + animationCompletion(); + + [viewController dismissViewControllerAnimated:NO completion:nil]; + viewController = nil; + failure(nil); + } onFailure:^(NSError * _Nonnull error) { + + animationCompletion(); + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + [viewController dismissViewControllerAnimated:NO completion:nil]; + viewController = nil; + failure(error); + }]; +} + @end diff --git a/Riot/Modules/KeyVerification/Common/KeyVerificationService.swift b/Riot/Modules/KeyVerification/Common/KeyVerificationService.swift index 4df2095f3..aeca8a37d 100644 --- a/Riot/Modules/KeyVerification/Common/KeyVerificationService.swift +++ b/Riot/Modules/KeyVerification/Common/KeyVerificationService.swift @@ -20,6 +20,9 @@ final class KeyVerificationService { private let cameraAccessManager: CameraAccessManager + private var authenticatedSessionFactory: AuthenticatedSessionViewControllerFactory? + private var supportSetupKeyVerificationByUser: [String: Bool] = [:] // Cached server response + init() { self.cameraAccessManager = CameraAccessManager() } diff --git a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift index 5ca915323..80c4128cb 100644 --- a/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift +++ b/Riot/Modules/Secrets/Setup/RecoveryKey/SecretsSetupRecoveryKeyViewModel.swift @@ -59,7 +59,7 @@ final class SecretsSetupRecoveryKeyViewModel: SecretsSetupRecoveryKeyViewModelTy private func createSecureKey() { self.update(viewState: .loading) - self.recoveryService.createRecovery(forSecrets: nil, withPassphrase: self.passphrase, success: { secretStorageKeyCreationInfo in + self.recoveryService.createRecovery(forSecrets: nil, withPassphrase: self.passphrase, createServicesBackups: true, success: { secretStorageKeyCreationInfo in self.update(viewState: .loaded(secretStorageKeyCreationInfo.recoveryKey)) }, failure: { error in self.update(viewState: .error(error)) diff --git a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift index f621bbe25..fbed5ce6e 100644 --- a/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift +++ b/Riot/Modules/SecureBackup/Setup/Intro/SecureBackupSetupIntroViewController.swift @@ -19,7 +19,8 @@ import UIKit protocol SecureBackupSetupIntroViewControllerDelegate: class { func secureBackupSetupIntroViewControllerDidTapUseKey(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) func secureBackupSetupIntroViewControllerDidTapUsePassphrase(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) - func secureBackupSetupIntroViewControllerDidCancel(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) + func secureBackupSetupIntroViewControllerDidCancel(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController, showSkipAlert: Bool) + func secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) } @objcMembers @@ -39,10 +40,17 @@ final class SecureBackupSetupIntroViewController: UIViewController { private var theme: Theme! + private var activityIndicatorPresenter: ActivityIndicatorPresenter! + private var errorPresenter: MXKErrorPresentation! + // MARK: Public weak var delegate: SecureBackupSetupIntroViewControllerDelegate? + // This is evil + // TODO: refactor it + weak var keyBackup: MXKeyBackup? + // MARK: - Setup class func instantiate() -> SecureBackupSetupIntroViewController { @@ -61,10 +69,18 @@ final class SecureBackupSetupIntroViewController: UIViewController { self.vc_removeBackTitle() self.setupViews() + self.activityIndicatorPresenter = ActivityIndicatorPresenter() + self.errorPresenter = MXKErrorAlertPresentation() + self.registerThemeServiceDidChangeThemeNotification() self.update(theme: self.theme) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.checkKeyBackup() + } + override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle } @@ -76,7 +92,7 @@ final class SecureBackupSetupIntroViewController: UIViewController { guard let self = self else { return } - self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self) + self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: true) } self.navigationItem.rightBarButtonItem = cancelBarButtonItem @@ -107,6 +123,19 @@ final class SecureBackupSetupIntroViewController: UIViewController { } } + private func renderLoading() { + self.activityIndicatorPresenter.presentActivityIndicator(on: self.view, animated: true) + } + + private func renderLoaded() { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + } + + private func render(error: Error) { + self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) + self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) + } + private func update(theme: Theme) { self.theme = theme @@ -130,4 +159,59 @@ final class SecureBackupSetupIntroViewController: UIViewController { @objc private func themeDidChange() { self.update(theme: ThemeService.shared().theme) } + + // TODO: To remove + private func checkKeyBackup() { + guard let keyBackup = self.keyBackup else { + return + } + + // If a backup already exists and we do not have the private key, + // we need to get this private key first. Ask the user to make a key backup restore to catch it + if keyBackup.keyBackupVersion != nil && keyBackup.hasPrivateKeyInCryptoStore == false { + + let alertContoller = UIAlertController(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorTitle, + message: VectorL10n.secureKeyBackupSetupExistingBackupErrorInfo, + preferredStyle: .alert) + + let connectAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorUnlockIt, style: .default) { (_) in + self.delegate?.secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(self) + } + + let resetAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorDeleteIt, style: .destructive) { (_) in + self.deleteKeybackup() + } + + let cancelAction = UIAlertAction(title: VectorL10n.cancel, style: .cancel) { (_) in + self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: false) + } + + alertContoller.addAction(connectAction) + alertContoller.addAction(resetAction) + alertContoller.addAction(cancelAction) + + self.present(alertContoller, animated: true) + } + } + + func deleteKeybackup() { + guard let keyBackup = self.keyBackup, let keybackupVersion = keyBackup.keyBackupVersion?.version else { + return + } + + self.renderLoading() + keyBackup.deleteVersion(keybackupVersion, success: { [weak self] in + guard let self = self else { + return + } + self.renderLoaded() + self.checkKeyBackup() + }, failure: { [weak self] (error) in + guard let self = self else { + return + } + + self.render(error: error) + }) + } } diff --git a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift index bdf462b09..145d57da4 100644 --- a/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift +++ b/Riot/Modules/SecureBackup/Setup/SecureBackupSetupCoordinator.swift @@ -26,8 +26,10 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { // MARK: Private private let navigationRouter: NavigationRouterType + private let session: MXSession private let recoveryService: MXRecoveryService - + private let keyBackup: MXKeyBackup? + // MARK: Public // Must be used only internally @@ -39,7 +41,9 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { init(session: MXSession) { self.navigationRouter = NavigationRouter(navigationController: RiotNavigationController()) + self.session = session self.recoveryService = session.crypto.recoveryService + self.keyBackup = session.crypto.backup } // MARK: - Public methods @@ -58,6 +62,7 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { private func createIntro() -> SecureBackupSetupIntroViewController { let introViewController = SecureBackupSetupIntroViewController.instantiate() introViewController.delegate = self + introViewController.keyBackup = self.keyBackup return introViewController } @@ -109,6 +114,18 @@ final class SecureBackupSetupCoordinator: SecureBackupSetupCoordinatorType { self.navigationRouter.present(alertController, animated: true) } + private func showKeyBackupRestore() { + guard let keyBackupVersion = self.keyBackup?.keyBackupVersion else { + return + } + + let coordinator = KeyBackupRecoverCoordinator(session: self.session, keyBackupVersion: keyBackupVersion, navigationRouter: self.navigationRouter) + + self.add(childCoordinator: coordinator) + coordinator.delegate = self + coordinator.start() // Will trigger view controller push + } + private func didCancel(showSkipAlert: Bool = true) { if showSkipAlert { self.showCancelAlert() @@ -133,8 +150,12 @@ extension SecureBackupSetupCoordinator: SecureBackupSetupIntroViewControllerDele self.showSetupPassphrase() } - func secureBackupSetupIntroViewControllerDidCancel(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) { - self.didCancel() + func secureBackupSetupIntroViewControllerDidCancel(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController, showSkipAlert: Bool) { + self.didCancel(showSkipAlert: showSkipAlert) + } + + func secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) { + self.showKeyBackupRestore() } } @@ -169,3 +190,14 @@ extension SecureBackupSetupCoordinator: SecretsSetupRecoveryPassphraseCoordinato self.didCancel() } } + +// MARK: - KeyBackupRecoverCoordinatorDelegate +extension SecureBackupSetupCoordinator: KeyBackupRecoverCoordinatorDelegate { + func keyBackupRecoverCoordinatorDidCancel(_ keyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType) { + self.navigationRouter.popToRootModule(animated: true) + } + + func keyBackupRecoverCoordinatorDidRecover(_ keyBackupRecoverCoordinator: KeyBackupRecoverCoordinatorType) { + self.navigationRouter.popToRootModule(animated: true) + } +} diff --git a/Riot/Modules/Settings/Security/SecurityViewController.m b/Riot/Modules/Settings/Security/SecurityViewController.m index 9a558b262..8d6862475 100644 --- a/Riot/Modules/Settings/Security/SecurityViewController.m +++ b/Riot/Modules/Settings/Security/SecurityViewController.m @@ -29,19 +29,18 @@ #import "Riot-Swift.h" -// Dev flag for demoing what could be the settings -// There is still a lot of TODO behind -//#define NEW_CROSS_SIGNING_FLOW +// Dev flag to have more options +//#define CROSS_SIGNING_AND_BACKUP_DEV enum { SECTION_CRYPTO_SESSIONS, - SECTION_CROSSSIGNING, -#ifdef NEW_CROSS_SIGNING_FLOW SECTION_SECURE_BACKUP, -#endif SECTION_CRYPTOGRAPHY, +#ifdef CROSS_SIGNING_AND_BACKUP_DEV + SECTION_CROSSSIGNING, SECTION_KEYBACKUP, +#endif SECTION_ADVANCED, SECTION_COUNT }; @@ -60,7 +59,9 @@ enum { // - Advice them to do a recovery if local keys are obsolete -> We cannot know now // - Advice them to fix a secure backup if there is 4S but no key backup // - Warm them if there is no 4S and they do not have all 3 signing keys locally. They will set up a not complete secure backup +#ifdef CROSS_SIGNING_AND_BACKUP_DEV SECURE_BACKUP_INFO, +#endif SECURE_BACKUP_SETUP, SECURE_BACKUP_RESTORE, SECURE_BACKUP_DELETE, @@ -82,9 +83,11 @@ enum { @interface SecurityViewController () < +#ifdef CROSS_SIGNING_AND_BACKUP_DEV SettingsKeyBackupTableViewSectionDelegate, KeyBackupSetupCoordinatorBridgePresenterDelegate, KeyBackupRecoverCoordinatorBridgePresenterDelegate, +#endif UIDocumentInteractionControllerDelegate, SecretsRecoveryCoordinatorBridgePresenterDelegate, SecureBackupSetupCoordinatorBridgePresenterDelegate> @@ -112,9 +115,12 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> // The current pushed view controller UIViewController *pushedViewController; +#ifdef CROSS_SIGNING_AND_BACKUP_DEV SettingsKeyBackupTableViewSection *keyBackupSection; KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter; +#endif KeyBackupRecoverCoordinatorBridgePresenter *keyBackupRecoverCoordinatorBridgePresenter; + SecretsRecoveryCoordinatorBridgePresenter *secretsRecoveryCoordinatorBridgePresenter; } @@ -166,6 +172,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> self.tableView.rowHeight = UITableViewAutomaticDimension; self.tableView.estimatedRowHeight = 50; +#ifdef CROSS_SIGNING_AND_BACKUP_DEV if (self.mainSession.crypto.backup) { MXDeviceInfo *deviceInfo = [self.mainSession.crypto.deviceList storedDevice:self.mainSession.matrixRestClient.credentials.userId @@ -177,6 +184,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> keyBackupSection.delegate = self; } } +#endif // Observe user interface theme change. kThemeServiceDidChangeThemeNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kThemeServiceDidChangeThemeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { @@ -232,8 +240,11 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> kThemeServiceDidChangeThemeNotificationObserver = nil; } +#ifdef CROSS_SIGNING_AND_BACKUP_DEV keyBackupSetupCoordinatorBridgePresenter = nil; +#endif keyBackupRecoverCoordinatorBridgePresenter = nil; + } - (void)viewWillAppear:(BOOL)animated @@ -489,12 +500,10 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> case MXCrossSigningStateCanCrossSign: crossSigningInformation = [NSBundle mxk_localizedStringForKey:@"security_settings_crosssigning_info_ok"]; -#ifdef NEW_CROSS_SIGNING_FLOW if (![self.mainSession.crypto.recoveryService hasSecretLocally:MXSecretId.crossSigningMaster]) { crossSigningInformation = [crossSigningInformation stringByAppendingString:@"\n\n⚠️ The MSK is missing. Verify this device again or use the Secure Backup below to synchronise your keys accross your devices"]; } -#endif break; } @@ -569,7 +578,18 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> - (void)setupCrossSigning:(id)sender { -#ifdef NEW_CROSS_SIGNING_FLOW + [self setupCrossSigningWithTitle:@"Set up cross-signing" // TODO + message:NSLocalizedStringFromTable(@"security_settings_user_password_description", @"Vector", nil) + success:^{ + } failure:^(NSError *error) { + }]; +} + +- (void)setupCrossSigningWithTitle:(NSString*)title + message:(NSString*)message + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ __block UIViewController *viewController; [self startActivityIndicator]; @@ -578,8 +598,8 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> _authenticatedSessionViewControllerFactory = [[AuthenticatedSessionViewControllerFactory alloc] initWithSession:self.mainSession]; [_authenticatedSessionViewControllerFactory viewControllerForPath:path httpMethod:@"POST" - title:@"Set up cross-signing" // TODO - message:@"Confirm your identity by entering your account password" // TODO + title:title + message:message onViewController:^(UIViewController * _Nonnull theViewController) { viewController = theViewController; @@ -596,11 +616,13 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> [crossSigning setupWithAuthParams:authParams success:^{ [self stopActivityIndicator]; [self reloadData]; + success(); } failure:^(NSError * _Nonnull error) { [self stopActivityIndicator]; [self reloadData]; [[AppDelegate theDelegate] showErrorAsAlert:error]; + failure(error); }]; } @@ -609,6 +631,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> [viewController dismissViewControllerAnimated:NO completion:nil]; viewController = nil; + failure(nil); } onFailure:^(NSError * _Nonnull error) { [self stopActivityIndicator]; @@ -616,11 +639,8 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> [viewController dismissViewControllerAnimated:NO completion:nil]; viewController = nil; + failure(error); }]; - -#else - [self displayComingSoon]; -#endif } - (void)resetCrossSigning:(id)sender @@ -678,12 +698,10 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> - (void)refreshSecureBackupSectionData { - // TODO MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService; if (recoveryService.hasRecovery) { secureBackupSectionState = @[ - @(SECURE_BACKUP_INFO), @(SECURE_BACKUP_RESTORE), @(SECURE_BACKUP_DELETE), @(SECURE_BACKUP_DESCRIPTION), @@ -692,24 +710,17 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> } else { - if (self.canSetupSecureBackup) - { - secureBackupSectionState = @[ - @(SECURE_BACKUP_INFO), - @(SECURE_BACKUP_SETUP), // TODO: Check we have all keys locally (at least MSK, SSK & SSK) - @(SECURE_BACKUP_DESCRIPTION), - //@(SECURE_BACKUP_MANAGE_MANUALLY), - ]; - } - else - { - secureBackupSectionState = @[ - @(SECURE_BACKUP_INFO), - @(SECURE_BACKUP_DESCRIPTION), - //@(SECURE_BACKUP_MANAGE_MANUALLY), - ]; - } + secureBackupSectionState = @[ + @(SECURE_BACKUP_SETUP), + @(SECURE_BACKUP_DESCRIPTION), + //@(SECURE_BACKUP_MANAGE_MANUALLY), + ]; } + +#ifdef CROSS_SIGNING_AND_BACKUP_DEV + secureBackupSectionState = [@[@(SECURE_BACKUP_INFO)] arrayByAddingObjectsFromArray:secureBackupSectionState]; +#endif + } - (NSUInteger)secureBackupSectionEnumForRow:(NSUInteger)row @@ -832,8 +843,6 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> { // Accept to create a setup only if we have the 3 cross-signing keys // This is the path to have a sane state - // TODO: What about missing MSK that was not gossiped before? - MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService; NSArray *crossSigningServiceSecrets = @[ @@ -847,16 +856,30 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> - (void)setupSecureBackup { -#ifdef NEW_CROSS_SIGNING_FLOW + if (self.canSetupSecureBackup) + { + [self setupSecureBackup2]; + } + else + { + // Set up cross-signing first + [self setupCrossSigningWithTitle:NSLocalizedStringFromTable(@"secure_key_backup_setup_intro_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"security_settings_user_password_description", @"Vector", nil) + success:^{ + [self setupSecureBackup2]; + } failure:^(NSError *error) { + }]; + } +} + +- (void)setupSecureBackup2 +{ SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; secureBackupSetupCoordinatorBridgePresenter.delegate = self; [secureBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES]; self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter; -#else - [self displayComingSoon]; -#endif } - (void)restoreFromSecureBackup @@ -873,7 +896,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> if (recoveryService) { [self startActivityIndicator]; - [recoveryService deleteRecoveryWithSuccess:^{ + [recoveryService deleteRecoveryWithDeleteServicesBackups:YES success:^{ [self stopActivityIndicator]; [self reloadData]; } failure:^(NSError * _Nonnull error) { @@ -919,17 +942,17 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> count = devicesArray.count + 1; } break; -#ifdef NEW_CROSS_SIGNING_FLOW case SECTION_SECURE_BACKUP: count = [self numberOfRowsInSecureBackupSection]; break; -#endif +#ifdef CROSS_SIGNING_AND_BACKUP_DEV case SECTION_KEYBACKUP: count = keyBackupSection.numberOfRows; break; case SECTION_CROSSSIGNING: count = [self numberOfRowsInCrossSigningSection]; break; +#endif case SECTION_CRYPTOGRAPHY: count = CRYPTOGRAPHY_COUNT; break; @@ -1112,7 +1135,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> else { cell = [self descriptionCellForTableView:tableView - withText:NSLocalizedStringFromTable(@"security_settings_crypto_sessions_description", @"Vector", nil) ]; + withText:NSLocalizedStringFromTable(@"security_settings_crypto_sessions_description_2", @"Vector", nil) ]; } } else @@ -1124,42 +1147,32 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> else if (row == devicesArray.count) { cell = [self descriptionCellForTableView:tableView - withText:NSLocalizedStringFromTable(@"security_settings_crypto_sessions_description", @"Vector", nil) ]; + withText:NSLocalizedStringFromTable(@"security_settings_crypto_sessions_description_2", @"Vector", nil) ]; } } } -#ifdef NEW_CROSS_SIGNING_FLOW else if (section == SECTION_SECURE_BACKUP) { switch ([self secureBackupSectionEnumForRow:row]) { case SECURE_BACKUP_DESCRIPTION: { - // TODO cell = [self descriptionCellForTableView:tableView - //withText:@"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server."]; - //withText:@"Back up your encryption keys with your account data in case you lose access to your logins. Your keys will be secured with a unique Recovery Key."]; - withText:@"Back up your encryption keys with your account data in case you lose access to your logins. Your keys are secured with a Recovery Key or a Secret Phrase."]; + withText:NSLocalizedStringFromTable(@"security_settings_secure_backup_description", @"Vector", nil)]; break; } +#ifdef CROSS_SIGNING_AND_BACKUP_DEV case SECURE_BACKUP_INFO: { - // TODO cell = [self descriptionCellForTableView:tableView withText:self.secureBackupInformation]; break; } +#endif case SECURE_BACKUP_SETUP: { - // TODO: Button or cell? -// MXKTableViewCellWithTextView *textCell = [self textViewCellForTableView:tableView atIndexPath:indexPath]; -// textCell.mxkTextView.text = @"Set up Secure Backup"; // TODO -// textCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; -// -// cell = textCell; - - MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:@"Set up Secure Backup" // TODO + MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:NSLocalizedStringFromTable(@"security_settings_secure_backup_setup", @"Vector", nil) action:@selector(setupSecureBackup) forTableView:tableView atIndexPath:indexPath]; @@ -1169,17 +1182,17 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> } case SECURE_BACKUP_RESTORE: { - MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:@"Synchronise (Restore and/or Back up)" // TODO - action:@selector(restoreFromSecureBackup) - forTableView:tableView - atIndexPath:indexPath]; + MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:NSLocalizedStringFromTable(@"security_settings_secure_backup_synchronise", @"Vector", nil) + action:@selector(restoreFromSecureBackup) + forTableView:tableView + atIndexPath:indexPath]; cell = buttonCell; break; } case SECURE_BACKUP_DELETE: { - MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:@"Delete Secure Backup" // TODO + MXKTableViewCellWithButton *buttonCell = [self buttonCellWithTitle:NSLocalizedStringFromTable(@"security_settings_secure_backup_delete", @"Vector", nil) action:@selector(deleteSecureBackup) forTableView:tableView atIndexPath:indexPath]; @@ -1201,7 +1214,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> } } -#endif +#ifdef CROSS_SIGNING_AND_BACKUP_DEV else if (section == SECTION_KEYBACKUP) { cell = [keyBackupSection cellForRowAtRow:row]; @@ -1225,6 +1238,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> break; } } +#endif else if (section == SECTION_CRYPTOGRAPHY) { switch (row) @@ -1283,14 +1297,14 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> { case SECTION_CRYPTO_SESSIONS: return NSLocalizedStringFromTable(@"security_settings_crypto_sessions", @"Vector", nil); -#ifdef NEW_CROSS_SIGNING_FLOW case SECTION_SECURE_BACKUP: - return @"SECURE BACKUP"; // TODO -#endif + return NSLocalizedStringFromTable(@"security_settings_secure_backup", @"Vector", nil); +#ifdef CROSS_SIGNING_AND_BACKUP_DEV case SECTION_KEYBACKUP: return NSLocalizedStringFromTable(@"security_settings_backup", @"Vector", nil); case SECTION_CROSSSIGNING: return NSLocalizedStringFromTable(@"security_settings_crosssigning", @"Vector", nil); +#endif case SECTION_CRYPTOGRAPHY: return NSLocalizedStringFromTable(@"security_settings_cryptography", @"Vector", nil); case SECTION_ADVANCED: @@ -1513,7 +1527,7 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> #pragma mark - SettingsKeyBackupTableViewSectionDelegate - +#ifdef CROSS_SIGNING_AND_BACKUP_DEV - (void)settingsKeyBackupTableViewSectionDidUpdate:(SettingsKeyBackupTableViewSection *)settingsKeyBackupTableViewSection { [self.tableView reloadData]; @@ -1624,6 +1638,8 @@ SecureBackupSetupCoordinatorBridgePresenterDelegate> [keyBackupSection reload]; } +#endif + #pragma mark - KeyBackupRecoverCoordinatorBridgePresenter - (void)showKeyBackupRecover:(MXKeyBackupVersion*)keyBackupVersion fromViewController:(UIViewController*)presentingViewController diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 01be56d64..be33654b8 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -148,7 +148,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(void); @interface SettingsViewController () // The current pushed view controller UIViewController *pushedViewController; - KeyBackupSetupCoordinatorBridgePresenter *keyBackupSetupCoordinatorBridgePresenter; - SettingsIdentityServerCoordinatorBridgePresenter *identityServerSettingsCoordinatorBridgePresenter; } @@ -255,6 +253,9 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> @property (nonatomic, strong) SettingsDiscoveryTableViewSection *settingsDiscoveryTableViewSection; @property (nonatomic, strong) SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter; +@property (nonatomic, strong) SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter; +@property (nonatomic, strong) AuthenticatedSessionViewControllerFactory *authenticatedSessionViewControllerFactory; + @end @implementation SettingsViewController @@ -429,7 +430,7 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [super destroy]; } - keyBackupSetupCoordinatorBridgePresenter = nil; + _secureBackupSetupCoordinatorBridgePresenter = nil; identityServerSettingsCoordinatorBridgePresenter = nil; } @@ -4053,35 +4054,72 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> [deactivateAccountViewController dismissViewControllerAnimated:YES completion:nil]; } +#pragma mark - SecureBackupSetupCoordinatorBridgePresenter -#pragma mark - KeyBackupSetupCoordinatorBridgePresenter - -- (void)showKeyBackupSetupFromSignOutFlow:(BOOL)showFromSignOutFlow +- (void)showSecureBackupSetupFromSignOutFlow { - keyBackupSetupCoordinatorBridgePresenter = [[KeyBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; - - [keyBackupSetupCoordinatorBridgePresenter presentFrom:self - isStartedFromSignOut:showFromSignOutFlow - animated:true]; - - keyBackupSetupCoordinatorBridgePresenter.delegate = self; + if (self.canSetupSecureBackup) + { + [self setupSecureBackup2]; + } + else + { + // Set up cross-signing first + [self setupCrossSigningWithTitle:NSLocalizedStringFromTable(@"secure_key_backup_setup_intro_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"security_settings_user_password_description", @"Vector", nil) + success:^{ + [self setupSecureBackup2]; + } failure:^(NSError *error) { + }]; + } } -- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { - [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; - keyBackupSetupCoordinatorBridgePresenter = nil; +- (void)setupSecureBackup2 +{ + SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession]; + secureBackupSetupCoordinatorBridgePresenter.delegate = self; + + [secureBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES]; + + self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter; } -- (void)keyBackupSetupCoordinatorBridgePresenterDelegateDidSetupRecoveryKey:(KeyBackupSetupCoordinatorBridgePresenter *)bridgePresenter { - [keyBackupSetupCoordinatorBridgePresenter dismissWithAnimated:true]; - keyBackupSetupCoordinatorBridgePresenter = nil; +- (BOOL)canSetupSecureBackup +{ + // Accept to create a setup only if we have the 3 cross-signing keys + // This is the path to have a sane state + // TODO: What about missing MSK that was not gossiped before? + + MXRecoveryService *recoveryService = self.mainSession.crypto.recoveryService; + + NSArray *crossSigningServiceSecrets = @[ + MXSecretId.crossSigningMaster, + MXSecretId.crossSigningSelfSigning, + MXSecretId.crossSigningUserSigning]; + + return ([recoveryService.secretsStoredLocally mx_intersectArray:crossSigningServiceSecrets].count + == crossSigningServiceSecrets.count); +} + +#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate + +- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.secureBackupSetupCoordinatorBridgePresenter = nil; +} + +- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter +{ + [self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil]; + self.secureBackupSetupCoordinatorBridgePresenter = nil; } #pragma mark - SignOutAlertPresenterDelegate - (void)signOutAlertPresenterDidTapBackupAction:(SignOutAlertPresenter * _Nonnull)presenter { - [self showKeyBackupSetupFromSignOutFlow:YES]; + [self showSecureBackupSetupFromSignOutFlow]; } - (void)signOutAlertPresenterDidTapSignOutAction:(SignOutAlertPresenter * _Nonnull)presenter @@ -4105,6 +4143,63 @@ SettingsIdentityServerCoordinatorBridgePresenterDelegate> }]; } +- (void)setupCrossSigningWithTitle:(NSString*)title + message:(NSString*)message + success:(void (^)(void))success + failure:(void (^)(NSError *error))failure +{ + __block UIViewController *viewController; + [self startActivityIndicator]; + + // Get credentials to set up cross-signing + NSString *path = [NSString stringWithFormat:@"%@/keys/device_signing/upload", kMXAPIPrefixPathUnstable]; + _authenticatedSessionViewControllerFactory = [[AuthenticatedSessionViewControllerFactory alloc] initWithSession:self.mainSession]; + [_authenticatedSessionViewControllerFactory viewControllerForPath:path + httpMethod:@"POST" + title:title + message:message + onViewController:^(UIViewController * _Nonnull theViewController) + { + viewController = theViewController; + [self presentViewController:viewController animated:YES completion:nil]; + + } onAuthenticated:^(NSDictionary * _Nonnull authParams) { + + [viewController dismissViewControllerAnimated:NO completion:nil]; + viewController = nil; + + MXCrossSigning *crossSigning = self.mainSession.crypto.crossSigning; + if (crossSigning) + { + [crossSigning setupWithAuthParams:authParams success:^{ + [self stopActivityIndicator]; + success(); + } failure:^(NSError * _Nonnull error) { + [self stopActivityIndicator]; + + [[AppDelegate theDelegate] showErrorAsAlert:error]; + failure(error); + }]; + } + + } onCancelled:^{ + [self stopActivityIndicator]; + + [viewController dismissViewControllerAnimated:NO completion:nil]; + viewController = nil; + failure(nil); + } onFailure:^(NSError * _Nonnull error) { + + [self stopActivityIndicator]; + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + [viewController dismissViewControllerAnimated:NO completion:nil]; + viewController = nil; + failure(error); + }]; +} + + #pragma mark - SingleImagePickerPresenterDelegate - (void)singleImagePickerPresenterDidCancel:(SingleImagePickerPresenter *)presenter diff --git a/Riot/Modules/Settings/SignOut/SignOutAlertPresenter.swift b/Riot/Modules/Settings/SignOut/SignOutAlertPresenter.swift index 83bfa03f2..0d9e4c3e4 100644 --- a/Riot/Modules/Settings/SignOut/SignOutAlertPresenter.swift +++ b/Riot/Modules/Settings/SignOut/SignOutAlertPresenter.swift @@ -89,7 +89,7 @@ final class SignOutAlertPresenter: NSObject { self.presentNonExistingBackupSignOutConfirmationAlert(animated: true) } - let setUpKeyBackupAction = UIAlertAction(title: VectorL10n.signOutNonExistingKeyBackupAlertSetupKeyBackupAction, style: .default) { (_) in + let setUpKeyBackupAction = UIAlertAction(title: VectorL10n.signOutNonExistingKeyBackupAlertSetupSecureBackupAction, style: .default) { (_) in self.delegate?.signOutAlertPresenterDidTapBackupAction(self) }