Implemented setting database password in iOS

This commit is contained in:
dankito 2020-10-14 00:18:36 +02:00
parent 5c63af15a0
commit 5acb2353c7
5 changed files with 131 additions and 74 deletions

View File

@ -35,18 +35,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
private func setupBankingUi() -> AuthenticationService {
let appDataFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
?? Bundle.main.resourceURL?.absoluteString ?? ""
let persistence = CoreDataBankingPersistence()
let authenticationService = AuthenticationService()
let authenticationService = AuthenticationService(persistence)
self.persistence = persistence
let dataFolder = URL(fileURLWithPath: "data", isDirectory: true, relativeTo: URL(fileURLWithPath: appDataFolder))
let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, transactionPartySearcher: persistence, bankIconFinder: SwiftBankIconFinder(), serializer: NoOpSerializer(), asyncRunner: DispatchQueueAsyncRunner())
DependencyInjector.register(dependency: presenter)
DependencyInjector.register(dependency: authenticationService)
return authenticationService
@ -60,7 +52,18 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
private func showApplicationMainView(window: UIWindow) {
window.rootViewController = UINavigationController(rootViewController: TabBarController())
if let persistence = persistence {
let appDataFolder = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
?? Bundle.main.resourceURL?.absoluteString ?? ""
let dataFolder = URL(fileURLWithPath: "data", isDirectory: true, relativeTo: URL(fileURLWithPath: appDataFolder))
let presenter = BankingPresenterSwift(dataFolder: dataFolder, router: SwiftUiRouter(), webClient: UrlSessionWebClient(), persistence: persistence, transactionPartySearcher: persistence, bankIconFinder: SwiftBankIconFinder(), serializer: NoOpSerializer(), asyncRunner: DispatchQueueAsyncRunner())
DependencyInjector.register(dependency: presenter)
window.rootViewController = UINavigationController(rootViewController: TabBarController())
}
}

View File

@ -1,5 +1,6 @@
import SwiftUI
import LocalAuthentication
import BankingUiSwift
class AuthenticationService {
@ -12,13 +13,22 @@ class AuthenticationService {
private let biometricAuthenticationService = BiometricAuthenticationService()
private let persistence: IBankingPersistence
init() {
init(_ persistence: IBankingPersistence) {
self.persistence = persistence
if let type = readAuthenticationType() {
self.authenticationType = type
if type == .none {
openDatabase(false, nil)
}
}
else { // first app run, no authentication type persisted yet -> set to .unprotected
removeAppProtection()
openDatabase(false, nil)
}
}
@ -68,6 +78,41 @@ class AuthenticationService {
}
func authenticateUserWithBiometric(_ prompt: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
biometricAuthenticationService.authenticate(prompt) { successful, error in
var decryptDatabaseResult = false
if successful {
decryptDatabaseResult = self.openDatabase(true, nil)
}
authenticationResult(successful && decryptDatabaseResult, error)
}
}
func authenticateUserWithPassword(_ enteredPassword: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
if retrieveLoginPassword() == enteredPassword {
let decryptDatabaseResult = openDatabase(false, enteredPassword)
authenticationResult(decryptDatabaseResult, nil)
}
else {
authenticationResult(false, "Incorrect password entered".localize())
}
}
@discardableResult
private func openDatabase(_ useBiometricAuthentication: Bool, _ userLoginPassword: String?) -> Bool {
if var databasePassword = readDefaultPassword(useBiometricAuthentication) {
if let loginPassword = userLoginPassword {
databasePassword = concatPasswords(loginPassword, databasePassword)
}
return persistence.decryptData(password: map(databasePassword))
}
return false
}
func setAuthenticationMethodToPassword(_ newLoginPassword: String) {
setAuthenticationType(.password)
@ -144,10 +189,10 @@ class AuthenticationService {
if let newLoginPassword = newLoginPassword {
setLoginPassword(newLoginPassword)
databasePassword = newLoginPassword + "_" + databasePassword
databasePassword = concatPasswords(newLoginPassword, databasePassword)
}
return true
return persistence.changePassword(newPassword: map(databasePassword))
} catch {
NSLog("Could not save default password: \(error)")
}
@ -172,6 +217,18 @@ class AuthenticationService {
return nil
}
private func readDefaultPassword(_ useBiometricAuthentication: Bool) -> String? {
do {
let passwordItem = createDefaultPasswordKeychainItem(useBiometricAuthentication)
return try passwordItem.readPassword()
} catch {
NSLog("Could not read default password: \(error)")
}
return nil
}
private func createDefaultPasswordKeychainItem(_ useBiometricAuthentication: Bool) -> KeychainPasswordItem {
var accessControl: SecAccessControl? = nil
var context: LAContext? = nil
@ -237,24 +294,25 @@ class AuthenticationService {
}
func authenticateUserWithBiometric(_ prompt: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
biometricAuthenticationService.authenticate(prompt, authenticationResult)
}
func authenticateUserWithPassword(_ enteredPassword: String, _ authenticationResult: @escaping (Bool, String?) -> Void) {
if retrieveLoginPassword() == enteredPassword {
authenticationResult(true, nil)
}
else {
authenticationResult(false, "Incorrect password entered".localize())
}
}
private func generateRandomPassword(_ passwordLength: Int) -> String {
let dictionary = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789§±!@#$%^&*-_=+;:|/?.>,<"
return String((0 ..< passwordLength).map{ _ in dictionary.randomElement()! })
}
private func concatPasswords(_ loginPassword: String, _ defaultPassword: String) -> String {
return loginPassword + "_" + defaultPassword
}
private func map(_ string: String) -> KotlinCharArray {
let array = KotlinCharArray(size: Int32(string.count))
for i in 0 ..< string.count {
array.set(index: Int32(i), value: (string as NSString).character(at: i))
}
return array
}
}

View File

@ -10,53 +10,13 @@ class CoreDataBankingPersistence: IBankingPersistence, ITransactionPartySearcher
private let mapper = Mapper()
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "BankingiOSApp")
do {
let options = [
EncryptedStorePassphraseKey : "someKey"
]
let description = try EncryptedStore.makeDescription(options: options, configuration: nil)
container.persistentStoreDescriptions = [ description ]
}
catch {
NSLog("Could not initialize encrypted database storage: " + error.localizedDescription)
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
private var persistentContainer: NSPersistentContainer?
lazy var context: NSManagedObjectContext = {
return persistentContainer.viewContext
return persistentContainer!.viewContext
}()
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
@ -71,11 +31,46 @@ class CoreDataBankingPersistence: IBankingPersistence, ITransactionPartySearcher
func decryptData(password: KotlinCharArray) -> Bool {
return true
do {
let container = NSPersistentContainer(name: "BankingiOSApp")
let options = [
EncryptedStorePassphraseKey : map(password)
]
let description = try EncryptedStore.makeDescription(options: options, configuration: nil)
container.persistentStoreDescriptions = [ description ]
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
self.persistentContainer = container
return true
}
catch {
NSLog("Could not initialize encrypted database storage: " + error.localizedDescription)
}
return false
}
func changePassword(newPassword: KotlinCharArray) -> Bool {
if let encryptedStore = persistentContainer.persistentStoreCoordinator.persistentStores.first { $0 is EncryptedStore } as? EncryptedStore {
if let encryptedStore = persistentContainer?.persistentStoreCoordinator.persistentStores.first { $0 is EncryptedStore } as? EncryptedStore {
do {
let result = try encryptedStore.changeDatabasePassphrase(map(newPassword))

View File

@ -130,7 +130,7 @@ struct LoginDialog: View {
struct LoginDialog_Previews: PreviewProvider {
static var previews: some View {
LoginDialog(AuthenticationService()) { _ in }
LoginDialog(AuthenticationService(CoreDataBankingPersistence())) { _ in }
}
}

View File

@ -43,10 +43,11 @@ struct ProtectAppSettingsDialog: View {
init() {
let currentAuthenticationType = authenticationService.authenticationType
let isBiometricAuthenticationSupported = authenticationService.deviceSupportsFaceID || authenticationService.deviceSupportsTouchID
var authenticationTypes = [AuthenticationType]()
if authenticationService.deviceSupportsFaceID || authenticationService.deviceSupportsTouchID {
if isBiometricAuthenticationSupported {
authenticationTypes.append(.biometric)
}
@ -59,7 +60,7 @@ struct ProtectAppSettingsDialog: View {
self.supportedAuthenticationTypes = authenticationTypes
if currentAuthenticationType == .biometric || currentAuthenticationType != .password {
if currentAuthenticationType == .biometric && isBiometricAuthenticationSupported {
if authenticationService.deviceSupportsFaceID {
_isFaceIDSelected = State(initialValue: true)
}