From f5014283b1e97be06e644535b92fab6b461a7e57 Mon Sep 17 00:00:00 2001 From: Kayla Galway Date: Wed, 8 Apr 2020 12:16:58 -0600 Subject: [PATCH] Resolves database access issues if locally stored salt or key is incorrect (#1202) * Resolves database access issues if locally stored salt or key is incorrect. --- Shared/Store/BaseDataStore.swift | 66 +++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/Shared/Store/BaseDataStore.swift b/Shared/Store/BaseDataStore.swift index 21110877d..dbdb4cf1f 100644 --- a/Shared/Store/BaseDataStore.swift +++ b/Shared/Store/BaseDataStore.swift @@ -388,13 +388,17 @@ extension BaseDataStore { private func unlockInternal() { guard let loginsStorage = loginsStorage, let loginsKey = loginsKey, - let salt = salt else { return } + let salt = salt, + let loginsDatabasePath = loginsDatabasePath else { return } do { try loginsStorage.ensureUnlockedWithKeyAndSalt(key: loginsKey, salt: salt) self.storageStateSubject.onNext(.Unlocked) } catch let error as LoginsStoreError { pushError(error) + // If we can not access database with current salt and key, need to delete local database and migrate to replacement salt + // This only deletes the local database file, does not delete the user's sync data + handleDatabaseAccessFailure(databasePath: loginsDatabasePath, encryptionKey: loginsKey) } catch let error { NSLog("Unknown error unlocking: \(error)") } @@ -468,13 +472,13 @@ extension BaseDataStore { guard let loginsDatabasePath = loginsDatabasePath, let loginsKey = loginsKey else { return nil } - let key = KeychainKey.salt.rawValue - if keychainWrapper.hasValue(forKey: key, withAccessibility: .afterFirstUnlock) { - return keychainWrapper.string(forKey: key, withAccessibility: .afterFirstUnlock) + let saltKey = KeychainKey.salt.rawValue + if keychainWrapper.hasValue(forKey: saltKey, withAccessibility: .afterFirstUnlock) { + return keychainWrapper.string(forKey: saltKey, withAccessibility: .afterFirstUnlock) } let val = setupPlaintextHeaderAndGetSalt(databasePath: loginsDatabasePath, encryptionKey: loginsKey) - keychainWrapper.set(val, forKey: key, withAccessibility: .afterFirstUnlock) + keychainWrapper.set(val, forKey: saltKey, withAccessibility: .afterFirstUnlock) return val } @@ -487,19 +491,63 @@ extension BaseDataStore { guard let db = loginsStorage as? LoginsStorage else { return createRandomSalt() } - + do { let salt = try db.getDbSaltForKey(key: encryptionKey) try db.migrateToPlaintextHeader(key: encryptionKey, salt: salt) return salt } catch { - print("setupPlaintextHeaderAndGetSalt failed with error: \(error)") self.dispatcher.dispatch(action: SentryAction(title: "setupPlaintextHeaderAndGetSalt failed", error: error, line: nil)) - // the database exists. but we didn't store the salt? return createRandomSalt() } } - + + // Closes database + // Deletes database file + // Creates new database and syncs + private func handleDatabaseAccessFailure(databasePath: String, encryptionKey: String) { + let saltKey = KeychainKey.salt.rawValue + if keychainWrapper.hasValue(forKey: saltKey, withAccessibility: .afterFirstUnlock) { + keychainWrapper.removeObject(forKey: saltKey) + } + do { + if let database = loginsStorage as? LoginsStorage { + database.close() + } + if FileManager.default.fileExists(atPath: databasePath) { + try FileManager.default.removeItem(atPath: databasePath) + loginsStorage = nil + try createNewDatabase() + } else { + loginsStorage = nil + try createNewDatabase() + } + } catch { + self.dispatcher.dispatch(action: SentryAction(title: "handleDatabaseAccessFailure failed", error: error, line: nil)) + } + } + + enum DatabaseError: Error { + case issueDeletingDatabase(description: String) + case issueCreatingDatabase(description: String) + } + + private func createNewDatabase() throws { + guard let encryptionKey = loginsKey else { throw DatabaseError.issueCreatingDatabase(description: "logins database key is nil") } + do { + initializeLoginsStorage() + guard let newDatabase = loginsStorage as? LoginsStorage else { throw DatabaseError.issueCreatingDatabase(description: "initializing new database failed") } + let salt = createRandomSalt() + try newDatabase.ensureUnlockedWithKeyAndSalt(key: encryptionKey, salt: salt) + let saltKey = KeychainKey.salt.rawValue + keychainWrapper.set(salt, forKey: saltKey, withAccessibility: .afterFirstUnlock) + self.storageStateSubject.onNext(.Unlocked) + } catch { + self.dispatcher.dispatch(action: SentryAction(title: "handleDatabaseAccessFailure failed", error: error, line: nil)) + throw DatabaseError.issueCreatingDatabase(description: "failed to unlock new database with key and salt:\(error)") + } + } + private func createRandomSalt() -> String { return UUID().uuidString.replacingOccurrences(of: "-", with: "") }