Skip to content
This repository has been archived by the owner on Dec 14, 2021. It is now read-only.

Resolve local database access issues #1202

Merged
merged 3 commits into from
Apr 8, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 57 additions & 9 deletions Shared/Store/BaseDataStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)")
}
Expand Down Expand Up @@ -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
}

Expand All @@ -487,19 +491,63 @@ extension BaseDataStore {
guard let db = loginsStorage as? LoginsStorage else {
return createRandomSalt()
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: whitespace

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)
}
Comment on lines +530 to +533

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏


private func createNewDatabase() throws {
guard let encryptionKey = loginsKey else { throw DatabaseError.issueCreatingDatabase(description: "logins database key is nil") }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

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: "")
}
Expand Down