diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index c8a685075..ec2379900 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -277,6 +277,7 @@ 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; 4681820102DAC8BA586357D4 /* VoiceMessageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB8D7926A5684E18196B538 /* VoiceMessageCache.swift */; }; 46A261AA898344A1F3C406B1 /* ReportContentScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCE3636E3D01477C8B2E9D0 /* ReportContentScreenModels.swift */; }; + 46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */; }; 46BA7F4B4D3A7164DED44B88 /* FullscreenDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565F1B2B300597C616B37888 /* FullscreenDialog.swift */; }; 46C9F8FE3810A04A005FE16B /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B19B2BCC779ED934E0BBC2A /* AudioPlayer.swift */; }; 4714991754A08B58B4D7ED85 /* OnboardingScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2F27BAB69EB568369F1F6B3 /* OnboardingScreenViewModelProtocol.swift */; }; @@ -987,6 +988,7 @@ FB595EC9C00AB32F39034055 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A37E2FACFD041CE466223CD /* SceneDelegate.swift */; }; FB9A1DD83EF641A75ABBCE69 /* WaitlistScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C796FC1DFDBCDD5573D0360F /* WaitlistScreenViewModelTests.swift */; }; FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */; }; + FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */; }; FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */; }; FC10228E73323BDC09526F97 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; }; FC4F6BA083A64840B38CE269 /* SecureBackupRecoveryKeyScreenUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDBA358C79F0DCBC4FA14A88 /* SecureBackupRecoveryKeyScreenUITests.swift */; }; @@ -1185,6 +1187,7 @@ 225EFCA26877E75CDFE7F48D /* MapTilerStyleBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTilerStyleBuilderProtocol.swift; sourceTree = ""; }; 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = ""; }; 227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; + 2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProvider.swift; sourceTree = ""; }; 2389732B0E115A999A069083 /* NotificationSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsScreenCoordinator.swift; sourceTree = ""; }; 23AA3F4B285570805CB0CCDD /* MapTiler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapTiler.swift; sourceTree = ""; }; 24227FF9A2797F6EA7F69CDD /* HomeScreenInvitesButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInvitesButton.swift; sourceTree = ""; }; @@ -1879,6 +1882,7 @@ E6F5D66F158A6662F953733E /* NotificationSettingsProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationSettingsProxy.swift; sourceTree = ""; }; E6FCC416A3BFE73DF7B3E6BF /* RoomTimelineControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomTimelineControllerFactory.swift; sourceTree = ""; }; E71C28CF29CD05B6D6AE8580 /* HomeScreenSessionVerificationBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenSessionVerificationBanner.swift; sourceTree = ""; }; + E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProviderProtocol.swift; sourceTree = ""; }; E8294DB9E95C0C0630418466 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; E8774CF614849664B5B3C2A1 /* UserSessionFlowCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionFlowCoordinatorStateMachine.swift; sourceTree = ""; }; E8A1BBEF7318CA6B6ACCF4AE /* AppLockSetupUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockSetupUITests.swift; sourceTree = ""; }; @@ -4310,6 +4314,8 @@ CA555F7C7CA382ACACF0D82B /* Keychain */ = { isa = PBXGroup; children = ( + 2355398E4A55DA5A89128AD1 /* EncryptionKeyProvider.swift */, + E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */, E36CB905A2B9EC2C92A2DA7C /* KeychainController.swift */, 39C0D861FC397AC34BCF089E /* KeychainControllerMock.swift */, E5E94DCFEE803E5ABAE8ACCE /* KeychainControllerProtocol.swift */, @@ -5477,6 +5483,8 @@ 9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */, 4C5A638DAA8AF64565BA4866 /* EncryptedRoomTimelineItem.swift in Sources */, B5903E48CF43259836BF2DBF /* EncryptedRoomTimelineView.swift in Sources */, + FBD402E3170EB1ED0D1AA672 /* EncryptionKeyProvider.swift in Sources */, + 46A6DB0F78FB399BD59E2D41 /* EncryptionKeyProviderProtocol.swift in Sources */, 50539366B408780B232C1910 /* EstimatedWaveformView.swift in Sources */, F78BAD28482A467287A9A5A3 /* EventBasedMessageTimelineItemProtocol.swift in Sources */, 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */, diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index be634caad..b09f30111 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -393,7 +393,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, private func startAuthentication() { let authenticationNavigationStackCoordinator = NavigationStackCoordinator() - let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, appSettings: appSettings) + let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, + encryptionKeyProvider: EncryptionKeyProvider(), + appSettings: appSettings) authenticationCoordinator = AuthenticationCoordinator(authenticationService: authenticationService, navigationStackCoordinator: authenticationNavigationStackCoordinator, appSettings: appSettings, @@ -418,7 +420,9 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, userDisplayName: userSession.clientProxy.userDisplayName.value ?? "", deviceID: userSession.deviceID) - let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, appSettings: appSettings) + let authenticationService = AuthenticationServiceProxy(userSessionStore: userSessionStore, + encryptionKeyProvider: EncryptionKeyProvider(), + appSettings: appSettings) _ = await authenticationService.configure(for: userSession.homeserver) let parameters = SoftLogoutScreenCoordinatorParameters(authenticationService: authenticationService, diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index 6a7239335..1022e33f3 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -72,7 +72,8 @@ final class AppSettings { // MARK: - Application - lazy var canShowDeveloperOptions: Bool = { + /// Whether or not the app is a development build that isn't in production. + lazy var isDevelopmentBuild: Bool = { #if DEBUG true #else diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 0ac810545..74697edd8 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -1,4 +1,4 @@ -// Generated using Sourcery 2.1.2 — https://github.com/krzysztofzablocki/Sourcery +// Generated using Sourcery 2.1.3 — https://github.com/krzysztofzablocki/Sourcery // DO NOT EDIT // swiftlint:disable all diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift index 913e250a0..60b9787fa 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenInteractionHandler.swift @@ -112,7 +112,7 @@ class RoomScreenInteractionHandler { } var debugActions: [TimelineItemMenuAction] = [] - if appSettings.canShowDeveloperOptions || appSettings.viewSourceEnabled { + if appSettings.isDevelopmentBuild || appSettings.viewSourceEnabled { debugActions.append(.viewSource) } diff --git a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift index 66253331d..50ab440b8 100644 --- a/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsScreen/SettingsScreenViewModel.swift @@ -31,7 +31,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo userID: userSession.userID, accountProfileURL: userSession.clientProxy.accountURL(action: .profile), accountSessionsListURL: userSession.clientProxy.accountURL(action: .sessionsList), - showDeveloperOptions: appSettings.canShowDeveloperOptions), + showDeveloperOptions: appSettings.isDevelopmentBuild), imageProvider: userSession.mediaProvider) userSession.clientProxy.userAvatarURL diff --git a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift index 8a4f55a68..77804ecda 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift @@ -21,11 +21,18 @@ import MatrixRustSDK class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { private let authenticationService: AuthenticationService private let userSessionStore: UserSessionStoreProtocol + private let passphrase: String? private let homeserverSubject: CurrentValueSubject var homeserver: CurrentValuePublisher { homeserverSubject.asCurrentValuePublisher() } - init(userSessionStore: UserSessionStoreProtocol, appSettings: AppSettings) { + init(userSessionStore: UserSessionStoreProtocol, encryptionKeyProvider: EncryptionKeyProviderProtocol, appSettings: AppSettings) { + let passphrase = appSettings.isDevelopmentBuild ? encryptionKeyProvider.generateKey().base64EncodedString() : nil + if passphrase != nil { + MXLog.info("Testing database encryption in development build.") + } + + self.passphrase = passphrase self.userSessionStore = userSessionStore homeserverSubject = .init(LoginHomeserver(address: appSettings.defaultHomeserverAddress, @@ -41,7 +48,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { staticRegistrations: appSettings.oidcStaticRegistrations.mapKeys { $0.absoluteString }) authenticationService = AuthenticationService(basePath: userSessionStore.baseDirectory.path, - passphrase: nil, + passphrase: passphrase, userAgent: UserAgentBuilder.makeASCIIUserAgent(), oidcConfiguration: oidcConfiguration, customSlidingSyncProxy: appSettings.slidingSyncProxyURL?.absoluteString, @@ -138,7 +145,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { // MARK: - Private private func userSession(for client: Client) async -> Result { - switch await userSessionStore.userSession(for: client) { + switch await userSessionStore.userSession(for: client, passphrase: passphrase) { case .success(let clientProxy): return .success(clientProxy) case .failure: diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 34658a97c..2a3ad7e3f 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -15,9 +15,9 @@ // import Combine -import Foundation +import CryptoKit import MatrixRustSDK -import UIKit +import SwiftUI class ClientProxy: ClientProxyProtocol { private let client: ClientProtocol @@ -159,14 +159,22 @@ class ClientProxy: ClientProxyProtocol { client.homeserver() } - var restorationToken: RestorationToken? { + var session: Session? { do { - return try RestorationToken(session: client.session()) + return try client.session() } catch { - MXLog.error("Failed retrieving restore token with error: \(error)") + MXLog.error("Failed retrieving the client's session with error: \(error)") return nil } } + + private(set) lazy var pusherNotificationClientIdentifier: String? = { + // NOTE: The result is stored as part of the restoration token. Any changes + // here would require a migration to correctly match incoming notifications. + guard let data = userID.data(using: .utf8) else { return nil } + let digest = SHA256.hash(data: data) + return digest.compactMap { String(format: "%02x", $0) }.joined() + }() func startSync() { guard !hasEncounteredAuthError else { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 955bf59b4..62c115158 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -79,11 +79,13 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var homeserver: String { get } + var session: Session? { get } + var userDisplayName: CurrentValuePublisher { get } var userAvatarURL: CurrentValuePublisher { get } - - var restorationToken: RestorationToken? { get } + + var pusherNotificationClientIdentifier: String? { get } var roomSummaryProvider: RoomSummaryProviderProtocol? { get } diff --git a/ElementX/Sources/Services/Client/MockClientProxy.swift b/ElementX/Sources/Services/Client/MockClientProxy.swift index cceafe2cc..ae6775575 100644 --- a/ElementX/Sources/Services/Client/MockClientProxy.swift +++ b/ElementX/Sources/Services/Client/MockClientProxy.swift @@ -26,7 +26,8 @@ class MockClientProxy: ClientProxyProtocol { let userID: String let deviceID: String? let homeserver = "" - let restorationToken: RestorationToken? = nil + let session: Session? = nil + let pusherNotificationClientIdentifier: String? = nil var roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() diff --git a/ElementX/Sources/Services/Keychain/EncryptionKeyProvider.swift b/ElementX/Sources/Services/Keychain/EncryptionKeyProvider.swift new file mode 100644 index 000000000..e31d06eb5 --- /dev/null +++ b/ElementX/Sources/Services/Keychain/EncryptionKeyProvider.swift @@ -0,0 +1,26 @@ +// +// Copyright 2023 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 CryptoKit +import Foundation + +struct EncryptionKeyProvider: EncryptionKeyProviderProtocol { + func generateKey() -> Data { + SymmetricKey(size: .bits256).withUnsafeBytes { bytes in + Data(Array(bytes)) + } + } +} diff --git a/ElementX/Sources/Services/Keychain/EncryptionKeyProviderProtocol.swift b/ElementX/Sources/Services/Keychain/EncryptionKeyProviderProtocol.swift new file mode 100644 index 000000000..043757856 --- /dev/null +++ b/ElementX/Sources/Services/Keychain/EncryptionKeyProviderProtocol.swift @@ -0,0 +1,21 @@ +// +// Copyright 2023 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 + +protocol EncryptionKeyProviderProtocol { + func generateKey() -> Data +} diff --git a/ElementX/Sources/Services/Keychain/KeychainController.swift b/ElementX/Sources/Services/Keychain/KeychainController.swift index 4e279a97e..5cccb9bb7 100644 --- a/ElementX/Sources/Services/Keychain/KeychainController.swift +++ b/ElementX/Sources/Services/Keychain/KeychainController.swift @@ -113,7 +113,14 @@ class KeychainController: KeychainControllerProtocol { func saveSessionInKeychain(session: Session) { MXLog.info("Saving session changes in the keychain.") - let restorationToken = RestorationToken(session: session) + + guard let oldToken = restorationTokenForUsername(session.userId) else { + MXLog.error("Failed retrieving the restoration token for \(session.userId)") + fatalError("Something has gone mega wrong, all bets are off.") + } + let restorationToken = RestorationToken(session: session, + passphrase: oldToken.passphrase, + pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier) setRestorationToken(restorationToken, forUsername: session.userId) } diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift index 3a7dedb35..d277c7063 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift @@ -127,7 +127,7 @@ final class NotificationManager: NSObject, NotificationManagerProtocol { let defaultPayload = APNSPayload(aps: APSInfo(mutableContent: 1, alert: APSAlert(locKey: "Notification", locArgs: [])), - pusherNotificationClientIdentifier: clientProxy.restorationToken?.pusherNotificationClientIdentifier) + pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier) let configuration = try await PusherConfiguration(identifiers: .init(pushkey: deviceToken.base64EncodedString(), appId: appSettings.pusherAppId), diff --git a/ElementX/Sources/Services/UserSession/RestorationToken.swift b/ElementX/Sources/Services/UserSession/RestorationToken.swift index 4c5bf28fd..e6547e9c9 100644 --- a/ElementX/Sources/Services/UserSession/RestorationToken.swift +++ b/ElementX/Sources/Services/UserSession/RestorationToken.swift @@ -14,24 +14,13 @@ // limitations under the License. // -import CryptoKit import Foundation - import MatrixRustSDK struct RestorationToken: Codable, Equatable { let session: MatrixRustSDK.Session + let passphrase: String? let pusherNotificationClientIdentifier: String? - - init(session: MatrixRustSDK.Session) { - self.session = session - if let data = session.userId.data(using: .utf8) { - let digest = SHA256.hash(data: data) - pusherNotificationClientIdentifier = digest.compactMap { String(format: "%02x", $0) }.joined() - } else { - pusherNotificationClientIdentifier = nil - } - } } extension MatrixRustSDK.Session: Codable { diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 34540b45f..de1d335a7 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -68,13 +68,21 @@ class UserSessionStore: UserSessionStoreProtocol { } } - func userSession(for client: Client) async -> Result { - switch await setupProxyForClient(client) { - case .success(let clientProxy): + func userSession(for client: Client, passphrase: String?) async -> Result { + do { + let session = try client.session() + let userID = try client.userId() + let clientProxy = await setupProxyForClient(client) + + keychainController.setRestorationToken(RestorationToken(session: session, + passphrase: passphrase, + pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier), + forUsername: userID) + return .success(buildUserSessionWithClient(clientProxy)) - case .failure(let error): + } catch { MXLog.error("Failed creating user session with error: \(error)") - return .failure(error) + return .failure(.failedSettingUpSession) } } @@ -104,10 +112,14 @@ class UserSessionStore: UserSessionStoreProtocol { } private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result { + if credentials.restorationToken.passphrase != nil { + MXLog.info("Restoring client with encrypted store.") + } let builder = ClientBuilder() .basePath(path: baseDirectory.path) .username(username: credentials.userID) .homeserverUrl(url: credentials.restorationToken.session.homeserverUrl) + .passphrase(passphrase: credentials.restorationToken.passphrase) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) .enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier, sessionDelegate: keychainController) @@ -119,30 +131,18 @@ class UserSessionStore: UserSessionStoreProtocol { try client.restoreSession(session: credentials.restorationToken.session) return client } - return await setupProxyForClient(client) + return await .success(setupProxyForClient(client)) } catch { MXLog.error("Failed restoring login with error: \(error)") return .failure(.failedRestoringLogin) } } - private func setupProxyForClient(_ client: Client) async -> Result { - do { - let session = try client.session() - let userID = try client.userId() - - keychainController.setRestorationToken(RestorationToken(session: session), forUsername: userID) - } catch { - MXLog.error("Failed setting up user session with error: \(error)") - return .failure(.failedSettingUpSession) - } - - let clientProxy = await ClientProxy(client: client, - backgroundTaskService: backgroundTaskService, - appSettings: ServiceLocator.shared.settings, - networkMonitor: ServiceLocator.shared.networkMonitor) - - return .success(clientProxy) + private func setupProxyForClient(_ client: Client) async -> ClientProxyProtocol { + await ClientProxy(client: client, + backgroundTaskService: backgroundTaskService, + appSettings: ServiceLocator.shared.settings, + networkMonitor: ServiceLocator.shared.networkMonitor) } private func deleteSessionDirectory(for userID: String) { diff --git a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift index ddb484f54..b25b34950 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift @@ -42,8 +42,8 @@ protocol UserSessionStoreProtocol { /// Restores an existing user session. func restoreUserSession() async -> Result - /// Creates a user session for a new client from the SDK. - func userSession(for client: Client) async -> Result + /// Creates a user session for a new client from the SDK along with the passphrase used for the data stores. + func userSession(for client: Client, passphrase: String?) async -> Result /// Logs out of the specified session. func logout(userSession: UserSessionProtocol) diff --git a/NSE/Sources/Other/NSEUserSession.swift b/NSE/Sources/Other/NSEUserSession.swift index d5d935562..ea42b928e 100644 --- a/NSE/Sources/Other/NSEUserSession.swift +++ b/NSE/Sources/Other/NSEUserSession.swift @@ -28,9 +28,13 @@ final class NSEUserSession { init(credentials: KeychainCredentials, clientSessionDelegate: ClientSessionDelegate) throws { userID = credentials.userID + if credentials.restorationToken.passphrase != nil { + MXLog.info("Restoring client with encrypted store.") + } baseClient = try ClientBuilder() .basePath(path: URL.sessionsBaseDirectory.path) .username(username: credentials.userID) + .passphrase(passphrase: credentials.restorationToken.passphrase) .userAgent(userAgent: UserAgentBuilder.makeASCIIUserAgent()) .enableCrossProcessRefreshLock(processId: InfoPlistReader.main.bundleIdentifier, sessionDelegate: clientSessionDelegate) diff --git a/UnitTests/Sources/KeychainControllerTests.swift b/UnitTests/Sources/KeychainControllerTests.swift index 3f9913087..c5b4d34a8 100644 --- a/UnitTests/Sources/KeychainControllerTests.swift +++ b/UnitTests/Sources/KeychainControllerTests.swift @@ -39,7 +39,9 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncProxy: "https://my.sync.proxy")) + slidingSyncProxy: "https://my.sync.proxy"), + passphrase: "passphrase", + pusherNotificationClientIdentifier: "pusherClientID") keychain.setRestorationToken(restorationToken, forUsername: username) // Then the restoration token should be stored in the keychain. @@ -55,7 +57,9 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncProxy: "https://my.sync.proxy")) + slidingSyncProxy: "https://my.sync.proxy"), + passphrase: "passphrase", + pusherNotificationClientIdentifier: "pusherClientID") keychain.setRestorationToken(restorationToken, forUsername: username) XCTAssertEqual(keychain.restorationTokens().count, 1, "The keychain should have 1 restoration token.") XCTAssertEqual(keychain.restorationTokenForUsername(username), restorationToken, "The initial restoration token should match the value that was stored.") @@ -77,7 +81,9 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncProxy: "https://my.sync.proxy")) + slidingSyncProxy: "https://my.sync.proxy"), + passphrase: "passphrase", + pusherNotificationClientIdentifier: "pusherClientID") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") } XCTAssertEqual(keychain.restorationTokens().count, 5, "The keychain should have 5 restoration tokens.") @@ -98,7 +104,9 @@ class KeychainControllerTests: XCTestCase { deviceId: "deviceId", homeserverUrl: "homeserverUrl", oidcData: "oidcData", - slidingSyncProxy: "https://my.sync.proxy")) + slidingSyncProxy: "https://my.sync.proxy"), + passphrase: "passphrase", + pusherNotificationClientIdentifier: "pusherClientID") keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com") } XCTAssertEqual(keychain.restorationTokens().count, 5, "The keychain should have 5 restoration tokens.") diff --git a/changelog.d/441.wip b/changelog.d/441.wip new file mode 100644 index 000000000..0b995abd5 --- /dev/null +++ b/changelog.d/441.wip @@ -0,0 +1 @@ +Enable database encryption for new logins on Nightly/PR builds. \ No newline at end of file