From f7d878bac602e6a5fa4dc8f6850d9fd04ab090d7 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 18 Sep 2024 17:30:45 +0200 Subject: [PATCH] crypto: Add configuration flag to enable invisible crypto (#3247) --- ElementX.xcodeproj/project.pbxproj | 4 ---- ElementX/Sources/Application/AppSettings.swift | 15 ++++++++++++++- .../Sources/Other/Extensions/ClientBuilder.swift | 10 ++++++++-- .../DeveloperOptionsScreenModels.swift | 1 + .../View/DeveloperOptionsScreen.swift | 11 +++++++++++ .../AuthenticationClientBuilder.swift | 3 ++- .../Services/UserSession/UserSessionStore.swift | 3 ++- NSE/Sources/NotificationServiceExtension.swift | 5 +++-- NSE/Sources/Other/NSESettingsProtocol.swift | 14 -------------- NSE/Sources/Other/NSEUserSession.swift | 5 +++-- 10 files changed, 44 insertions(+), 27 deletions(-) delete mode 100644 NSE/Sources/Other/NSESettingsProtocol.swift diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 65aea85981..e88ea37a11 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -473,7 +473,6 @@ 6A54F52443EC52AC5CD772C0 /* JoinRoomScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 869A8A4632E511351BFE2EC4 /* JoinRoomScreen.swift */; }; 6AD722DD92E465E56D2885AB /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */; }; 6AEB650311F694A5702255C9 /* UserProfileScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5B4932E4EFBC8FAC10972CD /* UserProfileScreenCoordinator.swift */; }; - 6AECC84BE14A13440120FED8 /* NSESettingsProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */; }; 6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; }; 6BAD956B909A6E29F6CC6E7C /* ButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CC23C63849452BC86EA2852 /* ButtonStyle.swift */; }; 6BB6944443C421C722ED1E7D /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */; }; @@ -1874,7 +1873,6 @@ 9F1DF3FFFE5ED2B8133F43A7 /* MessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageComposer.swift; sourceTree = ""; }; 9F40FB0A43DAECEC27C73722 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/SAS.strings; sourceTree = ""; }; 9F85164F9475FF2867F71AAA /* RoomTimelineController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineController.swift; sourceTree = ""; }; - 9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESettingsProtocol.swift; sourceTree = ""; }; A00C7A331B72C0F05C00392F /* RoomScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenViewModelProtocol.swift; sourceTree = ""; }; A010B8EAD1A9F6B4686DF2F4 /* BlockedUsersScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModel.swift; sourceTree = ""; }; A019A12C866D64CF072024B9 /* AppLockSetupPINScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupPINScreenViewModel.swift; sourceTree = ""; }; @@ -3467,7 +3465,6 @@ children = ( 4959CECEC984B3995616F427 /* DataProtectionManager.swift */, D3D455BC2423D911A62ACFB2 /* NSELogger.swift */, - 9FB4F169D653296023ED65E6 /* NSESettingsProtocol.swift */, EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */, 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */, ); @@ -6021,7 +6018,6 @@ E2DB696117BAEABAD5718023 /* MediaSourceProxy.swift in Sources */, 4FC085B1E5D1EB804495E2F4 /* MockMediaProvider.swift in Sources */, 5455147CAC63F71E48F7D699 /* NSELogger.swift in Sources */, - 6AECC84BE14A13440120FED8 /* NSESettingsProtocol.swift in Sources */, 30CC4F796B27BE8B1DFDBF5A /* NSEUserSession.swift in Sources */, 1D5DC685CED904386C89B7DA /* NSRegularExpresion.swift in Sources */, 94F0B78928E952689ACDB271 /* NetworkMonitor.swift in Sources */, diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 4ecc857836..b6b9e74560 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -8,6 +8,12 @@ import Foundation import SwiftUI +// Common settings between app and NSE +protocol CommonSettingsProtocol { + var logLevel: TracingConfiguration.LogLevel { get } + var invisibleCryptoEnabled: Bool { get } +} + /// Store Element specific app settings. final class AppSettings { private enum UserDefaultsKeys: String { @@ -37,6 +43,7 @@ final class AppSettings { case publicSearchEnabled case fuzzyRoomListSearchEnabled case pinningEnabled + case invisibleCryptoEnabled } private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier @@ -270,11 +277,17 @@ final class AppSettings { enum SlidingSyncDiscovery: Codable { case proxy, native, forceNative } @UserPreference(key: UserDefaultsKeys.slidingSyncDiscovery, defaultValue: .native, storageType: .userDefaults(store)) var slidingSyncDiscovery: SlidingSyncDiscovery - + #endif // MARK: - Shared @UserPreference(key: UserDefaultsKeys.logLevel, defaultValue: TracingConfiguration.LogLevel.info, storageType: .userDefaults(store)) var logLevel + + /// Configuration to enable invisible crypto. In this mode only devices signed by their owner will be considered in e2ee rooms. + @UserPreference(key: UserDefaultsKeys.invisibleCryptoEnabled, defaultValue: false, storageType: .userDefaults(store)) + var invisibleCryptoEnabled } + +extension AppSettings: CommonSettingsProtocol { } diff --git a/ElementX/Sources/Other/Extensions/ClientBuilder.swift b/ElementX/Sources/Other/Extensions/ClientBuilder.swift index 23e835f770..586fa4c201 100644 --- a/ElementX/Sources/Other/Extensions/ClientBuilder.swift +++ b/ElementX/Sources/Other/Extensions/ClientBuilder.swift @@ -14,12 +14,12 @@ extension ClientBuilder { httpProxy: String? = nil, slidingSync: ClientBuilderSlidingSync, sessionDelegate: ClientSessionDelegate, - appHooks: AppHooks) -> ClientBuilder { + appHooks: AppHooks, + invisibleCryptoEnabled: Bool) -> ClientBuilder { var builder = ClientBuilder() .enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier, sessionDelegate: sessionDelegate) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) .requestConfig(config: .init(retryLimit: 0, timeout: 30000, maxConcurrentRequests: nil, retryTimeout: nil)) - .roomKeyRecipientStrategy(strategy: .deviceBasedStrategy(onlyAllowTrustedDevices: false, errorOnVerifiedUserProblem: true)) builder = switch slidingSync { case .restored: builder @@ -33,6 +33,12 @@ extension ClientBuilder { .autoEnableCrossSigning(autoEnableCrossSigning: true) .backupDownloadStrategy(backupDownloadStrategy: .afterDecryptionFailure) .autoEnableBackups(autoEnableBackups: true) + + if invisibleCryptoEnabled { + builder = builder.roomKeyRecipientStrategy(strategy: CollectStrategy.identityBasedStrategy) + } else { + builder = builder.roomKeyRecipientStrategy(strategy: .deviceBasedStrategy(onlyAllowTrustedDevices: false, errorOnVerifiedUserProblem: true)) + } } if let httpProxy { diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index 86d8964a3a..8ad26e512f 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -47,6 +47,7 @@ protocol DeveloperOptionsProtocol: AnyObject { var elementCallBaseURLOverride: URL? { get set } var fuzzyRoomListSearchEnabled: Bool { get set } var pinningEnabled: Bool { get set } + var invisibleCryptoEnabled: Bool { get set } } extension AppSettings: DeveloperOptionsProtocol { } diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index 5967f33ba6..7f9217f665 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -52,6 +52,17 @@ struct DeveloperOptionsScreen: View { } } + Section { + Toggle(isOn: $context.invisibleCryptoEnabled) { + Text("Enabled Invisible Crypto") + Text("Requires app reboot") + } + } header: { + Text("Trust and Decoration") + } footer: { + Text("This setting controls how end-to-end encryption (E2EE) keys are shared. Enabling it will prevent the inclusion of devices that have not been explicitly verified by their owners.") + } + Section { TextField(context.viewState.elementCallBaseURL.absoluteString, text: $elementCallURLOverrideString) .autocorrectionDisabled(true) diff --git a/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift b/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift index df337fb27a..350e388246 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationClientBuilder.swift @@ -69,7 +69,8 @@ struct AuthenticationClientBuilder { .baseBuilder(httpProxy: appSettings.websiteURL.globalProxy, slidingSync: slidingSync, sessionDelegate: clientSessionDelegate, - appHooks: appHooks) + appHooks: appHooks, + invisibleCryptoEnabled: appSettings.invisibleCryptoEnabled) .sessionPaths(dataPath: sessionDirectories.dataPath, cachePath: sessionDirectories.cachePath) .passphrase(passphrase: passphrase) diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index bd8f17342e..be2e2cf331 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -124,7 +124,8 @@ class UserSessionStore: UserSessionStoreProtocol { .baseBuilder(httpProxy: URL(string: homeserverURL)?.globalProxy, slidingSync: .restored, sessionDelegate: keychainController, - appHooks: appHooks) + appHooks: appHooks, + invisibleCryptoEnabled: appSettings.enableNotifications) .sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath, cachePath: credentials.restorationToken.sessionDirectories.cachePath) .username(username: credentials.userID) diff --git a/NSE/Sources/NotificationServiceExtension.swift b/NSE/Sources/NotificationServiceExtension.swift index ae119e96d6..5443c1ffc3 100644 --- a/NSE/Sources/NotificationServiceExtension.swift +++ b/NSE/Sources/NotificationServiceExtension.swift @@ -32,7 +32,7 @@ import UserNotifications // We keep a global `environment` singleton to ensure that our app context, // database, logging, etc. are only ever setup once per *process* -private let settings: NSESettingsProtocol = AppSettings() +private let settings: CommonSettingsProtocol = AppSettings() private let notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: RoomMessageEventStringBuilder(attributedStringBuilder: AttributedStringBuilder(mentionBuilder: PlainMentionBuilder()), prefix: .none)) private let keychainController = KeychainController(service: .sessions, accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier) @@ -81,7 +81,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension { do { Self.userSession = try await NSEUserSession(credentials: credentials, clientSessionDelegate: keychainController, - appHooks: appHooks) + appHooks: appHooks, + appSettings: settings) } catch { MXLog.error("Failed creating user session with error: \(error)") } diff --git a/NSE/Sources/Other/NSESettingsProtocol.swift b/NSE/Sources/Other/NSESettingsProtocol.swift deleted file mode 100644 index 677bc41c7d..0000000000 --- a/NSE/Sources/Other/NSESettingsProtocol.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Copyright 2023, 2024 New Vector Ltd. -// -// SPDX-License-Identifier: AGPL-3.0-only -// Please see LICENSE in the repository root for full details. -// - -import Foundation - -protocol NSESettingsProtocol { - var logLevel: TracingConfiguration.LogLevel { get } -} - -extension AppSettings: NSESettingsProtocol { } diff --git a/NSE/Sources/Other/NSEUserSession.swift b/NSE/Sources/Other/NSEUserSession.swift index 39a74d2d30..54e915f193 100644 --- a/NSE/Sources/Other/NSEUserSession.swift +++ b/NSE/Sources/Other/NSEUserSession.swift @@ -19,7 +19,7 @@ final class NSEUserSession { networkMonitor: nil) private let delegateHandle: TaskHandle? - init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate, appHooks: AppHooks) async throws { + init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate, appHooks: AppHooks, appSettings: CommonSettingsProtocol) async throws { sessionDirectories = credentials.restorationToken.sessionDirectories userID = credentials.userID @@ -33,7 +33,8 @@ final class NSEUserSession { httpProxy: URL(string: homeserverURL)?.globalProxy, slidingSync: .restored, sessionDelegate: clientSessionDelegate, - appHooks: appHooks) + appHooks: appHooks, + invisibleCryptoEnabled: appSettings.invisibleCryptoEnabled) .sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath, cachePath: credentials.restorationToken.sessionDirectories.cachePath) .username(username: credentials.userID)