|
| 1 | +import BitwardenKit |
| 2 | + |
| 3 | +// MARK: - ChangeKdfService |
| 4 | + |
| 5 | +/// A protocol for a `ChangeKdfService` which handles updating the KDF settings for a user. |
| 6 | +/// |
| 7 | +protocol ChangeKdfService { |
| 8 | + /// Returns whether the user needs to update their KDF settings to the minimum. |
| 9 | + /// |
| 10 | + /// - Returns: Whether the user needs to update their KDF settings to the minimum. |
| 11 | + /// |
| 12 | + func needsKdfUpdateToMinimums() async -> Bool |
| 13 | + |
| 14 | + /// Updates the user's KDF settings to the minimums. |
| 15 | + /// |
| 16 | + /// - Parameter password: The user's master password. |
| 17 | + /// |
| 18 | + func updateKdfToMinimums(password: String) async throws |
| 19 | +} |
| 20 | + |
| 21 | +extension ChangeKdfService { |
| 22 | + /// Updates the user's KDF settings to the minimums if their current settings are below the minimums. |
| 23 | + /// |
| 24 | + /// - Parameter password: The user's master password. |
| 25 | + /// |
| 26 | + func updateKdfToMinimumsIfNeeded(password: String) async throws { |
| 27 | + guard await needsKdfUpdateToMinimums() else { return } |
| 28 | + try await updateKdfToMinimums(password: password) |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +// MARK: - DefaultChangeKdfService |
| 33 | + |
| 34 | +/// A default implementation of a `ChangeKdfService`. |
| 35 | +/// |
| 36 | +class DefaultChangeKdfService: ChangeKdfService { |
| 37 | + // MARK: Properties |
| 38 | + |
| 39 | + /// The services used by the application to make account related API requests. |
| 40 | + private let accountAPIService: AccountAPIService |
| 41 | + |
| 42 | + /// The service that handles common client functionality such as encryption and decryption. |
| 43 | + private let clientService: ClientService |
| 44 | + |
| 45 | + /// The service to get server-specified configuration. |
| 46 | + private let configService: ConfigService |
| 47 | + |
| 48 | + /// The service used by the application to report non-fatal errors. |
| 49 | + private let errorReporter: ErrorReporter |
| 50 | + |
| 51 | + /// The service used by the application to manage account state. |
| 52 | + private let stateService: StateService |
| 53 | + |
| 54 | + // MARK: Initialization |
| 55 | + |
| 56 | + /// Initialize a `DefaultChangeKdfService`. |
| 57 | + /// |
| 58 | + /// - Parameters: |
| 59 | + /// - accountAPIService: The services used by the application to make account related API requests. |
| 60 | + /// - clientService: The service that handles common client functionality such as encryption and decryption. |
| 61 | + /// - configService: The service to get server-specified configuration. |
| 62 | + /// - errorReporter: The service used by the application to report non-fatal errors. |
| 63 | + /// - stateService: The service used by the application to manage account state. |
| 64 | + /// |
| 65 | + init( |
| 66 | + accountAPIService: AccountAPIService, |
| 67 | + clientService: ClientService, |
| 68 | + configService: ConfigService, |
| 69 | + errorReporter: ErrorReporter, |
| 70 | + stateService: StateService |
| 71 | + ) { |
| 72 | + self.accountAPIService = accountAPIService |
| 73 | + self.clientService = clientService |
| 74 | + self.configService = configService |
| 75 | + self.errorReporter = errorReporter |
| 76 | + self.stateService = stateService |
| 77 | + } |
| 78 | + |
| 79 | + // MARK: Methods |
| 80 | + |
| 81 | + func needsKdfUpdateToMinimums() async -> Bool { |
| 82 | + guard await configService.getFeatureFlag(.forceUpdateKdfSettings) else { return false } |
| 83 | + |
| 84 | + do { |
| 85 | + let account = try await stateService.getActiveAccount() |
| 86 | + guard account.kdf.kdfType == .pbkdf2sha256, |
| 87 | + account.kdf.kdfIterations < Constants.minimumPbkdf2IterationsForUpgrade else { |
| 88 | + return false |
| 89 | + } |
| 90 | + return true |
| 91 | + } catch { |
| 92 | + errorReporter.log(error: error) |
| 93 | + return false |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + func updateKdfToMinimums(password: String) async throws { |
| 98 | + guard await configService.getFeatureFlag(.forceUpdateKdfSettings) else { return } |
| 99 | + |
| 100 | + let account = try await stateService.getActiveAccount() |
| 101 | + |
| 102 | + do { |
| 103 | + let updateKdfResponse = try await clientService.crypto().makeUpdateKdf( |
| 104 | + password: password, |
| 105 | + kdf: account.kdf.sdkKdf |
| 106 | + ) |
| 107 | + try await accountAPIService.updateKdf( |
| 108 | + UpdateKdfRequestModel(response: updateKdfResponse) |
| 109 | + ) |
| 110 | + |
| 111 | + // TODO: Do we need to save updated KDF settings to Account/UserDefaults? |
| 112 | + |
| 113 | + } catch { |
| 114 | + // If an error occurs, log the error. Don't throw since that would block the vault unlocking. |
| 115 | + errorReporter.log(error: BitwardenError.generalError( |
| 116 | + type: "Force Update KDF Error", |
| 117 | + message: "Unable to update KDF settings (\(account.kdf)", |
| 118 | + error: error |
| 119 | + )) |
| 120 | + } |
| 121 | + } |
| 122 | +} |
0 commit comments