diff --git a/AuthenticatorShared/Core/Platform/Services/ServiceContainer.swift b/AuthenticatorShared/Core/Platform/Services/ServiceContainer.swift index ba5791d5..2dd4d2d6 100644 --- a/AuthenticatorShared/Core/Platform/Services/ServiceContainer.swift +++ b/AuthenticatorShared/Core/Platform/Services/ServiceContainer.swift @@ -124,7 +124,7 @@ public class ServiceContainer: Services { let timeProvider = CurrentTime() let cryptographyKeyService = CryptographyKeyService( - keychainRepository: keychainRepository + stateService: stateService ) let cryptographyService = DefaultCryptographyService( diff --git a/AuthenticatorShared/Core/Platform/Services/StateService.swift b/AuthenticatorShared/Core/Platform/Services/StateService.swift index 2516685e..0a029e33 100644 --- a/AuthenticatorShared/Core/Platform/Services/StateService.swift +++ b/AuthenticatorShared/Core/Platform/Services/StateService.swift @@ -23,6 +23,12 @@ protocol StateService: AnyObject { /// func getClearClipboardValue(userId: String?) async throws -> ClearClipboardValue + /// Gets the user's encryption secret key. + /// + /// - Returns: The user's encryption secret key. + /// + func getSecretKey(userId: String?) async throws -> String? + /// Get whether to show website icons. /// /// - Returns: Whether to show the website icons. @@ -47,6 +53,13 @@ protocol StateService: AnyObject { /// func setClearClipboardValue(_ clearClipboardValue: ClearClipboardValue?, userId: String?) async throws + /// Sets the user's encryption secreet key. + /// + /// - Parameters: + /// - key: The key to set + /// + func setSecretKey(_ key: String, userId: String?) async throws + /// Set whether to show the website icons. /// /// - Parameter showWebIcons: Whether to show the website icons. @@ -130,6 +143,11 @@ actor DefaultStateService: StateService { return appSettingsStore.clearClipboardValue(userId: userId) } + func getSecretKey(userId: String?) async throws -> String? { + let userId = try userId ?? getActiveAccountUserId() + return appSettingsStore.secretKey(userId: userId) + } + func getShowWebIcons() async -> Bool { !appSettingsStore.disableWebIcons } @@ -144,6 +162,11 @@ actor DefaultStateService: StateService { appSettingsStore.setClearClipboardValue(clearClipboardValue, userId: userId) } + func setSecretKey(_ key: String, userId: String?) async throws { + let userId = try userId ?? getActiveAccountUserId() + appSettingsStore.setSecretKey(key, userId: userId) + } + func setShowWebIcons(_ showWebIcons: Bool) async { appSettingsStore.disableWebIcons = !showWebIcons showWebIconsSubject.send(showWebIcons) diff --git a/AuthenticatorShared/Core/Platform/Services/Stores/AppSettingsStore.swift b/AuthenticatorShared/Core/Platform/Services/Stores/AppSettingsStore.swift index 91bbb4ab..3c89a78a 100644 --- a/AuthenticatorShared/Core/Platform/Services/Stores/AppSettingsStore.swift +++ b/AuthenticatorShared/Core/Platform/Services/Stores/AppSettingsStore.swift @@ -35,6 +35,13 @@ protocol AppSettingsStore: AnyObject { /// func clearClipboardValue(userId: String) -> ClearClipboardValue + /// Gets the user's secret encryption key. + /// + /// - Parameters: + /// - userId: The user ID + /// + func secretKey(userId: String) -> String? + /// Sets the time after which the clipboard should be cleared. /// /// - Parameters: @@ -44,6 +51,14 @@ protocol AppSettingsStore: AnyObject { /// - Returns: The time after which the clipboard should be cleared. /// func setClearClipboardValue(_ clearClipboardValue: ClearClipboardValue?, userId: String) + + /// Sets the user's secret encryption key. + /// + /// - Parameters: + /// - key: The key to set + /// - userId: The user ID + /// + func setSecretKey(_ key: String, userId: String) } // MARK: - DefaultAppSettingsStore @@ -167,6 +182,7 @@ extension DefaultAppSettingsStore: AppSettingsStore { case disableWebIcons case hasSeenWelcomeTutorial case migrationVersion + case secretKey(userId: String) /// Returns the key used to store the data under for retrieving it later. var storageKey: String { @@ -186,6 +202,8 @@ extension DefaultAppSettingsStore: AppSettingsStore { key = "hasSeenWelcomeTutorial" case .migrationVersion: key = "migrationVersion" + case let .secretKey(userId): + key = "secretKey_\(userId)" } return "bwaPreferencesStorage:\(key)" } @@ -224,6 +242,10 @@ extension DefaultAppSettingsStore: AppSettingsStore { return .never } + func secretKey(userId: String) -> String? { + return fetch(for: .secretKey(userId: userId)) + } + var migrationVersion: Int { get { fetch(for: .migrationVersion) } set { store(newValue, for: .migrationVersion) } @@ -232,4 +254,8 @@ extension DefaultAppSettingsStore: AppSettingsStore { func setClearClipboardValue(_ clearClipboardValue: ClearClipboardValue?, userId: String) { store(clearClipboardValue?.rawValue, for: .clearClipboardValue(userId: userId)) } + + func setSecretKey(_ key: String, userId: String) { + store(key, for: .secretKey(userId: userId)) + } } diff --git a/AuthenticatorShared/Core/Vault/Services/CryptographyKeyService.swift b/AuthenticatorShared/Core/Vault/Services/CryptographyKeyService.swift index 37056fa4..6ec34507 100644 --- a/AuthenticatorShared/Core/Vault/Services/CryptographyKeyService.swift +++ b/AuthenticatorShared/Core/Vault/Services/CryptographyKeyService.swift @@ -8,17 +8,17 @@ actor CryptographyKeyService { /// A repository to provide the encryption secret key /// - let keychainRepository: KeychainRepository + let stateService: StateService // MARK: Initialization /// Initialize a `CryptographyKeyService` /// /// - Parameters: - /// - keychainRepository: The repository for the keychain + /// - stateService: The state service for UserDefaults /// - init(keychainRepository: KeychainRepository) { - self.keychainRepository = keychainRepository + init(stateService: StateService) { + self.stateService = stateService } // MARK: Methods @@ -31,14 +31,14 @@ actor CryptographyKeyService { /// func getOrCreateSecretKey(userId: String) async throws -> SymmetricKey { do { - let secretKey = try await keychainRepository.getSecretKey(userId: userId) - guard let key = SymmetricKey(base64EncodedString: secretKey) else { + guard let secretKey = try await stateService.getSecretKey(userId: userId), + let key = SymmetricKey(base64EncodedString: secretKey) else { throw CryptographyError.unableToParseSecretKey } return key } catch { let key = SymmetricKey(size: .bits256) - try await keychainRepository.setSecretKey( + try await stateService.setSecretKey( key.base64EncodedString(), userId: userId )