Move dependency from KeychainSwift, and SamKaychain to main app, update with protocol definition #5255

pull/5263/head
Krypto Pank 2 years ago
parent 3d228a4dbd
commit af6688b0e4
  1. 4
      AlphaWallet.xcodeproj/project.pbxproj
  2. 11
      AlphaWallet/ActiveWalletCoordinator.swift
  3. 36
      AlphaWallet/AppCoordinator.swift
  4. 23
      AlphaWallet/AppDelegate.swift
  5. 87
      AlphaWallet/Common/Types/KeychainStorage.swift
  6. 12
      AlphaWallet/Lock/Coordinators/LockCreatePasscodeCoordinator.swift
  7. 34
      AlphaWallet/Lock/ViewControllers/LockCreatePasscodeViewController.swift
  8. 73
      AlphaWallet/Lock/ViewControllers/LockEnterPasscodeViewController.swift
  9. 6
      AlphaWallet/Lock/ViewControllers/LockPasscodeViewController.swift
  10. 6
      AlphaWallet/Lock/ViewModels/LockCreatePasscodeViewModel.swift
  11. 18
      AlphaWallet/Lock/ViewModels/LockEnterPasscodeViewModel.swift
  12. 10
      AlphaWallet/Lock/ViewModels/LockViewModel.swift
  13. 17
      AlphaWallet/Protection/Coordinators/LockEnterPasscodeCoordinator.swift
  14. 7
      AlphaWallet/Protection/Coordinators/ProtectionCoordinator.swift
  15. 8
      AlphaWallet/Settings/Coordinators/SettingsCoordinator.swift
  16. 3
      AlphaWallet/Settings/ViewControllers/SettingsViewController.swift
  17. 6
      AlphaWallet/Settings/ViewModels/SettingsViewModel.swift
  18. 21
      AlphaWalletTests/Coordinators/ActiveWalletViewTests.swift
  19. 21
      AlphaWalletTests/Coordinators/AppCoordinatorTests.swift
  20. 4
      AlphaWalletTests/Coordinators/LockCreatePasscodeCoordinatorTest.swift
  21. 15
      AlphaWalletTests/Coordinators/LockEnterPasscodeCoordinatorTest.swift
  22. 5
      AlphaWalletTests/EtherClient/EtherKeystoreTests.swift
  23. 8
      AlphaWalletTests/Factories/FakeEtherKeystore.swift
  24. 40
      AlphaWalletTests/Factories/FakeLockProtocol.swift
  25. 4
      Podfile.lock
  26. 4
      modules/AlphaWalletFoundation/AlphaWalletFoundation.podspec
  27. 5
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Core/Initializers/CleanupPasscode.swift
  28. 61
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Core/KeyManagement/EtherKeystore.swift
  29. 18
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Core/KeyManagement/LegacyFileBasedKeystore.swift
  30. 49
      modules/AlphaWalletFoundation/AlphaWalletFoundation/Core/Lock/Lock.swift

@ -669,6 +669,7 @@
87AE2736273D701A00E184D4 /* WalletConnectV2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87AE2735273D701A00E184D4 /* WalletConnectV2Types.swift */; }; 87AE2736273D701A00E184D4 /* WalletConnectV2Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87AE2735273D701A00E184D4 /* WalletConnectV2Types.swift */; };
87B1ACD928C0D45B0072A5E2 /* chains.zip in Resources */ = {isa = PBXBuildFile; fileRef = 87B1ACD828C0D45B0072A5E2 /* chains.zip */; }; 87B1ACD928C0D45B0072A5E2 /* chains.zip in Resources */ = {isa = PBXBuildFile; fileRef = 87B1ACD828C0D45B0072A5E2 /* chains.zip */; };
87B1ACDB28C0D55D0072A5E2 /* tokens.json in Resources */ = {isa = PBXBuildFile; fileRef = 87B1ACDA28C0D55D0072A5E2 /* tokens.json */; }; 87B1ACDB28C0D55D0072A5E2 /* tokens.json in Resources */ = {isa = PBXBuildFile; fileRef = 87B1ACDA28C0D55D0072A5E2 /* tokens.json */; };
87B1AD3728C1FE030072A5E2 /* KeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B1AD3628C1FE030072A5E2 /* KeychainStorage.swift */; };
87B651F7256D4BFE000EF927 /* ClaimPaidOrderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B651F6256D4BFE000EF927 /* ClaimPaidOrderCoordinator.swift */; }; 87B651F7256D4BFE000EF927 /* ClaimPaidOrderCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B651F6256D4BFE000EF927 /* ClaimPaidOrderCoordinator.swift */; };
87B93B0E2726A46500F6EA73 /* BrowserStorageSubscription.js in Resources */ = {isa = PBXBuildFile; fileRef = 87B93B0D2726A46500F6EA73 /* BrowserStorageSubscription.js */; }; 87B93B0E2726A46500F6EA73 /* BrowserStorageSubscription.js in Resources */ = {isa = PBXBuildFile; fileRef = 87B93B0D2726A46500F6EA73 /* BrowserStorageSubscription.js */; };
87B93B0F2726C59D00F6EA73 /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = 8703FC0A270366DA0062C416 /* content.js */; }; 87B93B0F2726C59D00F6EA73 /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = 8703FC0A270366DA0062C416 /* content.js */; };
@ -1506,6 +1507,7 @@
87AE2735273D701A00E184D4 /* WalletConnectV2Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectV2Types.swift; sourceTree = "<group>"; }; 87AE2735273D701A00E184D4 /* WalletConnectV2Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletConnectV2Types.swift; sourceTree = "<group>"; };
87B1ACD828C0D45B0072A5E2 /* chains.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; name = chains.zip; path = AlphaWallet/Resources/chains.zip; sourceTree = SOURCE_ROOT; }; 87B1ACD828C0D45B0072A5E2 /* chains.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; name = chains.zip; path = AlphaWallet/Resources/chains.zip; sourceTree = SOURCE_ROOT; };
87B1ACDA28C0D55D0072A5E2 /* tokens.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tokens.json; path = AlphaWallet/Resources/tokens.json; sourceTree = SOURCE_ROOT; }; 87B1ACDA28C0D55D0072A5E2 /* tokens.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = tokens.json; path = AlphaWallet/Resources/tokens.json; sourceTree = SOURCE_ROOT; };
87B1AD3628C1FE030072A5E2 /* KeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStorage.swift; sourceTree = "<group>"; };
87B651F6256D4BFE000EF927 /* ClaimPaidOrderCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimPaidOrderCoordinator.swift; sourceTree = "<group>"; }; 87B651F6256D4BFE000EF927 /* ClaimPaidOrderCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClaimPaidOrderCoordinator.swift; sourceTree = "<group>"; };
87B93B0D2726A46500F6EA73 /* BrowserStorageSubscription.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = BrowserStorageSubscription.js; sourceTree = "<group>"; }; 87B93B0D2726A46500F6EA73 /* BrowserStorageSubscription.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = BrowserStorageSubscription.js; sourceTree = "<group>"; };
87B93B122726C75900F6EA73 /* helpers.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = helpers.js; sourceTree = "<group>"; }; 87B93B122726C75900F6EA73 /* helpers.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = helpers.js; sourceTree = "<group>"; };
@ -3769,6 +3771,7 @@
026340902850C7D50033E51B /* Configuration.swift */, 026340902850C7D50033E51B /* Configuration.swift */,
5E7C7D674F6B2415FB5552B0 /* CanOpenContractWebPage.swift */, 5E7C7D674F6B2415FB5552B0 /* CanOpenContractWebPage.swift */,
875CA8DF28BDEE3D0020FA48 /* SwitchCustomChainCallbackId.swift */, 875CA8DF28BDEE3D0020FA48 /* SwitchCustomChainCallbackId.swift */,
87B1AD3628C1FE030072A5E2 /* KeychainStorage.swift */,
); );
path = Types; path = Types;
sourceTree = "<group>"; sourceTree = "<group>";
@ -4609,6 +4612,7 @@
87BC890E26B7EDC6005482F4 /* WalletSummaryView.swift in Sources */, 87BC890E26B7EDC6005482F4 /* WalletSummaryView.swift in Sources */,
8750F91924EE66D700E19DFF /* GasSpeedView.swift in Sources */, 8750F91924EE66D700E19DFF /* GasSpeedView.swift in Sources */,
875CA90728BE4EA20020FA48 /* UIDevice.swift in Sources */, 875CA90728BE4EA20020FA48 /* UIDevice.swift in Sources */,
87B1AD3728C1FE030072A5E2 /* KeychainStorage.swift in Sources */,
295A59381F71C1B90092F0FC /* AccountsCoordinator.swift in Sources */, 295A59381F71C1B90092F0FC /* AccountsCoordinator.swift in Sources */,
87F15AF727F1EB7D00EB9787 /* SwapStepView.swift in Sources */, 87F15AF727F1EB7D00EB9787 /* SwapStepView.swift in Sources */,
879F185E26E74512000602F2 /* ButtonsBarBackgroundView.swift in Sources */, 879F185E26E74512000602F2 /* ButtonsBarBackgroundView.swift in Sources */,

@ -133,7 +133,8 @@ class ActiveWalletCoordinator: NSObject, Coordinator, DappRequestHandlerDelegate
private let store: RealmStore private let store: RealmStore
private let tokenSwapper: TokenSwapper private let tokenSwapper: TokenSwapper
private let tokensService: DetectedContractsProvideble & TokenProvidable & TokenAddable private let tokensService: DetectedContractsProvideble & TokenProvidable & TokenAddable
private let lock: Lock
init( init(
navigationController: UINavigationController = NavigationController(), navigationController: UINavigationController = NavigationController(),
walletAddressesStore: WalletAddressesStore, walletAddressesStore: WalletAddressesStore,
@ -160,8 +161,10 @@ class ActiveWalletCoordinator: NSObject, Coordinator, DappRequestHandlerDelegate
tokenCollection: TokenCollection, tokenCollection: TokenCollection,
importToken: ImportToken, importToken: ImportToken,
transactionsDataStore: TransactionDataStore, transactionsDataStore: TransactionDataStore,
tokensService: DetectedContractsProvideble & TokenProvidable & TokenAddable tokensService: DetectedContractsProvideble & TokenProvidable & TokenAddable,
lock: Lock
) { ) {
self.lock = lock
self.tokensService = tokensService self.tokensService = tokensService
self.transactionsDataStore = transactionsDataStore self.transactionsDataStore = transactionsDataStore
self.importToken = importToken self.importToken = importToken
@ -372,8 +375,8 @@ class ActiveWalletCoordinator: NSObject, Coordinator, DappRequestHandlerDelegate
walletBalanceService: walletBalanceService, walletBalanceService: walletBalanceService,
blockscanChatService: blockscanChatService, blockscanChatService: blockscanChatService,
blockiesGenerator: blockiesGenerator, blockiesGenerator: blockiesGenerator,
domainResolutionService: domainResolutionService domainResolutionService: domainResolutionService,
) lock: lock)
coordinator.rootViewController.tabBarItem = ActiveWalletViewModel.Tabs.settings.tabBarItem coordinator.rootViewController.tabBarItem = ActiveWalletViewModel.Tabs.settings.tabBarItem
coordinator.navigationController.configureForLargeTitles() coordinator.navigationController.configureForLargeTitles()
coordinator.delegate = self coordinator.delegate = self

@ -16,7 +16,7 @@ extension TokenScript {
class AppCoordinator: NSObject, Coordinator { class AppCoordinator: NSObject, Coordinator {
private let config = Config() private let config = Config()
private let legacyFileBasedKeystore: LegacyFileBasedKeystore private let legacyFileBasedKeystore: LegacyFileBasedKeystore
private let lock = Lock() private lazy var lock: Lock = SecuredLock(securedStorage: securedStorage)
private var keystore: Keystore private var keystore: Keystore
private let assetDefinitionStore = AssetDefinitionStore(baseTokenScriptFiles: TokenScript.baseTokenScriptFiles) private let assetDefinitionStore = AssetDefinitionStore(baseTokenScriptFiles: TokenScript.baseTokenScriptFiles)
private let window: UIWindow private let window: UIWindow
@ -32,6 +32,10 @@ class AppCoordinator: NSObject, Coordinator {
var promptBackupCoordinator: PromptBackupCoordinator? { var promptBackupCoordinator: PromptBackupCoordinator? {
return coordinators.compactMap { $0 as? PromptBackupCoordinator }.first return coordinators.compactMap { $0 as? PromptBackupCoordinator }.first
} }
private lazy var protectionCoordinator: ProtectionCoordinator = {
return ProtectionCoordinator(lock: lock)
}()
private lazy var universalLinkService: UniversalLinkService = { private lazy var universalLinkService: UniversalLinkService = {
let coordinator = UniversalLinkService(analytics: analytics) let coordinator = UniversalLinkService(analytics: analytics)
coordinator.delegate = self coordinator.delegate = self
@ -154,14 +158,15 @@ class AppCoordinator: NSObject, Coordinator {
}() }()
private lazy var sessionProvider = SessionsProvider(config: config, analytics: analytics) private lazy var sessionProvider = SessionsProvider(config: config, analytics: analytics)
private let securedStorage: SecuredPasswordStorage & SecuredStorage
init(window: UIWindow, analytics: AnalyticsServiceType, keystore: Keystore, walletAddressesStore: WalletAddressesStore, navigationController: UINavigationController = .withOverridenBarAppearence()) throws { init(window: UIWindow, analytics: AnalyticsServiceType, keystore: Keystore, walletAddressesStore: WalletAddressesStore, navigationController: UINavigationController = .withOverridenBarAppearence(), securedStorage: SecuredPasswordStorage & SecuredStorage) throws {
self.navigationController = navigationController self.navigationController = navigationController
self.window = window self.window = window
self.analytics = analytics self.analytics = analytics
self.keystore = keystore self.keystore = keystore
self.walletAddressesStore = walletAddressesStore self.walletAddressesStore = walletAddressesStore
self.legacyFileBasedKeystore = try LegacyFileBasedKeystore(keystore: keystore) self.securedStorage = securedStorage
self.legacyFileBasedKeystore = try LegacyFileBasedKeystore(securedStorage: securedStorage, keystore: keystore)
super.init() super.init()
window.rootViewController = navigationController window.rootViewController = navigationController
@ -188,6 +193,7 @@ class AppCoordinator: NSObject, Coordinator {
} }
func start() { func start() {
protectionCoordinator.didFinishLaunchingWithOptions()
initializers() initializers()
runServices() runServices()
appTracker.start() appTracker.start()
@ -208,6 +214,23 @@ class AppCoordinator: NSObject, Coordinator {
assetDefinitionStore.delegate = self assetDefinitionStore.delegate = self
} }
func applicationWillResignActive() {
protectionCoordinator.applicationWillResignActive()
}
func applicationDidBecomeActive() {
protectionCoordinator.applicationDidBecomeActive()
handleUniversalLinkInPasteboard()
}
func applicationDidEnterBackground() {
protectionCoordinator.applicationDidEnterBackground()
}
func applicationWillEnterForeground() {
protectionCoordinator.applicationWillEnterForeground()
}
private func setupSplashViewController(on navigationController: UINavigationController) { private func setupSplashViewController(on navigationController: UINavigationController) {
navigationController.viewControllers = [ navigationController.viewControllers = [
SplashViewController() SplashViewController()
@ -264,7 +287,8 @@ class AppCoordinator: NSObject, Coordinator {
tokenCollection: dep.pipeline, tokenCollection: dep.pipeline,
importToken: dep.importToken, importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore, transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService) tokensService: dep.tokensService,
lock: lock)
coordinator.delegate = self coordinator.delegate = self
@ -284,7 +308,7 @@ class AppCoordinator: NSObject, Coordinator {
ConfigureApp(), ConfigureApp(),
CleanupWallets(keystore: keystore, walletAddressesStore: walletAddressesStore, config: config), CleanupWallets(keystore: keystore, walletAddressesStore: walletAddressesStore, config: config),
SkipBackupFiles(legacyFileBasedKeystore: legacyFileBasedKeystore), SkipBackupFiles(legacyFileBasedKeystore: legacyFileBasedKeystore),
CleanupPasscode(keystore: keystore) CleanupPasscode(keystore: keystore, lock: lock)
] ]
initializers.forEach { $0.perform() } initializers.forEach { $0.perform() }

@ -7,10 +7,6 @@ import AlphaWalletFoundation
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow? var window: UIWindow?
private var appCoordinator: AppCoordinator! private var appCoordinator: AppCoordinator!
//This is separate coordinator for the protection of the sensitive information.
private lazy var protectionCoordinator: ProtectionCoordinator = {
return ProtectionCoordinator()
}()
private lazy var reportProvider: ReportProvider = { private lazy var reportProvider: ReportProvider = {
let provider = ReportProvider() let provider = ReportProvider()
guard !isRunningTests() && isAlphaWallet() else { return provider } guard !isRunningTests() && isAlphaWallet() else { return provider }
@ -36,12 +32,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
let analytics = AnalyticsService() let analytics = AnalyticsService()
let walletAddressesStore: WalletAddressesStore = EtherKeystore.migratedWalletAddressesStore(userDefaults: .standardOrForTests) let walletAddressesStore: WalletAddressesStore = EtherKeystore.migratedWalletAddressesStore(userDefaults: .standardOrForTests)
var keystore: Keystore = try EtherKeystore(walletAddressesStore: walletAddressesStore, analytics: analytics) let securedStorage: SecuredStorage & SecuredPasswordStorage = try KeychainStorage()
var keystore: Keystore = EtherKeystore(keychain: securedStorage, walletAddressesStore: walletAddressesStore, analytics: analytics)
let navigationController: UINavigationController = .withOverridenBarAppearence() let navigationController: UINavigationController = .withOverridenBarAppearence()
navigationController.view.backgroundColor = Colors.appWhite navigationController.view.backgroundColor = Colors.appWhite
appCoordinator = try AppCoordinator(window: window!, analytics: analytics, keystore: keystore, walletAddressesStore: walletAddressesStore, navigationController: navigationController) appCoordinator = try AppCoordinator(window: window!, analytics: analytics, keystore: keystore, walletAddressesStore: walletAddressesStore, navigationController: navigationController, securedStorage: securedStorage)
keystore.delegate = appCoordinator keystore.delegate = appCoordinator
appCoordinator.start() appCoordinator.start()
@ -54,7 +51,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
} catch { } catch {
} }
protectionCoordinator.didFinishLaunchingWithOptions()
return true return true
} }
@ -67,20 +63,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
} }
func applicationWillResignActive(_ application: UIApplication) { func applicationWillResignActive(_ application: UIApplication) {
protectionCoordinator.applicationWillResignActive() appCoordinator.applicationWillResignActive()
} }
func applicationDidBecomeActive(_ application: UIApplication) { func applicationDidBecomeActive(_ application: UIApplication) {
protectionCoordinator.applicationDidBecomeActive() appCoordinator.applicationDidBecomeActive()
appCoordinator.handleUniversalLinkInPasteboard()
} }
func applicationDidEnterBackground(_ application: UIApplication) { func applicationDidEnterBackground(_ application: UIApplication) {
protectionCoordinator.applicationDidEnterBackground() appCoordinator.applicationDidEnterBackground()
} }
func applicationWillEnterForeground(_ application: UIApplication) { func applicationWillEnterForeground(_ application: UIApplication) {
protectionCoordinator.applicationWillEnterForeground() appCoordinator.applicationWillEnterForeground()
} }
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool { func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool {
@ -111,9 +106,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
return handled return handled
} }
//TODO Handle SNS errors func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
//no op //no op
} }

@ -0,0 +1,87 @@
//
// KeychainStorage.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 02.09.2022.
//
import Foundation
import KeychainSwift
import SAMKeychain
import AlphaWalletFoundation
import LocalAuthentication
final class KeychainStorage: SecuredStorage, SecuredPasswordStorage {
private let keychain: KeychainSwift
init(keyPrefix: String = Constants.keychainKeyPrefix) throws {
let keychain = KeychainSwift(keyPrefix: Constants.keychainKeyPrefix)
keychain.synchronizable = false
self.keychain = keychain
if !UIApplication.shared.isProtectedDataAvailable {
throw EtherKeystoreError.protectionDisabled
}
}
var hasUserCancelledLastAccess: Bool {
return keychain.lastResultCode == errSecUserCanceled
}
var isDataNotFoundForLastAccess: Bool {
return keychain.lastResultCode == errSecItemNotFound
}
func set(_ value: String, forKey key: String, withAccess access: AccessOptions?) -> Bool {
return keychain.set(value, forKey: key, withAccess: access?.asKeychainOptions)
}
func set(_ value: Data, forKey key: String, withAccess access: AccessOptions?) -> Bool {
return keychain.set(value, forKey: key, withAccess: access?.asKeychainOptions)
}
func get(_ key: String, prompt: String?, withContext context: LAContext?) -> String? {
return keychain.get(key, prompt: prompt, withContext: context)
}
func getData(_ key: String, prompt: String?, withContext context: LAContext?) -> Data? {
return keychain.getData(key, prompt: prompt, withContext: context)
}
func delete(_ key: String) -> Bool {
return keychain.delete(key)
}
func password(forService service: String, account: String) -> String? {
return SAMKeychain.password(forService: service, account: account)
}
func setPasword(_ pasword: String, forService service: String, account: String) {
SAMKeychain.setPassword(pasword, forService: service, account: account)
}
func deletePasword(forService service: String, account: String) {
SAMKeychain.deletePassword(forService: service, account: account)
}
}
fileprivate extension AccessOptions {
var asKeychainOptions: KeychainSwiftAccessOptions {
switch self {
case .accessibleWhenUnlocked:
return .accessibleWhenUnlocked
case .accessibleWhenUnlockedThisDeviceOnly(let userPresenceRequired):
return .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: userPresenceRequired)
case .accessibleAfterFirstUnlock:
return .accessibleAfterFirstUnlock
case .accessibleAfterFirstUnlockThisDeviceOnly:
return .accessibleAfterFirstUnlockThisDeviceOnly
case .accessibleAlways:
return .accessibleAlways
case .accessibleWhenPasscodeSetThisDeviceOnly:
return .accessibleWhenPasscodeSetThisDeviceOnly
case .accessibleAlwaysThisDeviceOnly:
return .accessibleAlwaysThisDeviceOnly
}
}
}

@ -5,15 +5,17 @@ import UIKit
import AlphaWalletFoundation import AlphaWalletFoundation
class LockCreatePasscodeCoordinator: Coordinator { class LockCreatePasscodeCoordinator: Coordinator {
var coordinators: [Coordinator] = [] private let lock: Lock
private let model: LockCreatePasscodeViewModel
private let navigationController: UINavigationController private let navigationController: UINavigationController
lazy var lockViewController: LockCreatePasscodeViewController = { lazy var lockViewController: LockCreatePasscodeViewController = {
return LockCreatePasscodeViewController(model: model) let viewModel = LockCreatePasscodeViewModel(lock: lock)
return LockCreatePasscodeViewController(lockCreatePasscodeViewModel: viewModel)
}() }()
init(navigationController: UINavigationController, model: LockCreatePasscodeViewModel) { var coordinators: [Coordinator] = []
init(navigationController: UINavigationController, lock: Lock) {
self.lock = lock
self.navigationController = navigationController self.navigationController = navigationController
self.model = model
} }
func start() { func start() {
lockViewController.navigationItem.largeTitleDisplayMode = .never lockViewController.navigationItem.largeTitleDisplayMode = .never

@ -4,35 +4,45 @@
import UIKit import UIKit
class LockCreatePasscodeViewController: LockPasscodeViewController { class LockCreatePasscodeViewController: LockPasscodeViewController {
private lazy var lockCreatePasscodeViewModel: LockCreatePasscodeViewModel? = { private let lockCreatePasscodeViewModel: LockCreatePasscodeViewModel
return model as? LockCreatePasscodeViewModel
}() init(lockCreatePasscodeViewModel: LockCreatePasscodeViewModel) {
private var firstPasscode: String? self.lockCreatePasscodeViewModel = lockCreatePasscodeViewModel
super.init(model: lockCreatePasscodeViewModel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
title = lockCreatePasscodeViewModel?.title title = lockCreatePasscodeViewModel.title
lockView.lockTitle.text = lockCreatePasscodeViewModel?.initialLabelText lockView.lockTitle.text = lockCreatePasscodeViewModel.initialLabelText
} }
override func enteredPasscode(_ passcode: String) { override func enteredPasscode(_ passcode: String) {
super.enteredPasscode(passcode) super.enteredPasscode(passcode)
if let first = firstPasscode { if let first = lockCreatePasscodeViewModel.firstPasscode {
if passcode == first { if passcode == first {
lock.setPasscode(passcode: passcode) lockCreatePasscodeViewModel.lock.setPasscode(passcode: passcode)
finish(withResult: true, animated: true) finish(withResult: true, animated: true)
} else { } else {
lockView.shake() lockView.shake()
firstPasscode = nil lockCreatePasscodeViewModel.set(firstPasscode: nil)
showFirstPasscodeView() showFirstPasscodeView()
} }
} else { } else {
firstPasscode = passcode lockCreatePasscodeViewModel.set(firstPasscode: passcode)
showConfirmPasscodeView() showConfirmPasscodeView()
} }
} }
private func showFirstPasscodeView() { private func showFirstPasscodeView() {
lockView.lockTitle.text = lockCreatePasscodeViewModel?.initialLabelText lockView.lockTitle.text = lockCreatePasscodeViewModel.initialLabelText
} }
private func showConfirmPasscodeView() { private func showConfirmPasscodeView() {
lockView.lockTitle.text = lockCreatePasscodeViewModel?.confirmLabelText lockView.lockTitle.text = lockCreatePasscodeViewModel.confirmLabelText
} }
} }

@ -2,46 +2,49 @@
// Copyright © 2018 Stormbird PTE. LTD. // Copyright © 2018 Stormbird PTE. LTD.
import UIKit import UIKit
import LocalAuthentication
import AlphaWalletFoundation import AlphaWalletFoundation
class LockEnterPasscodeViewController: LockPasscodeViewController { class LockEnterPasscodeViewController: LockPasscodeViewController {
private lazy var lockEnterPasscodeViewModel: LockEnterPasscodeViewModel? = { private let lockEnterPasscodeViewModel: LockEnterPasscodeViewModel
return model as? LockEnterPasscodeViewModel
}()
private var context: LAContext!
private var canEvaluatePolicy: Bool {
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
var unlockWithResult: ((_ success: Bool, _ bioUnlock: Bool) -> Void)? var unlockWithResult: ((_ success: Bool, _ bioUnlock: Bool) -> Void)?
init(lockEnterPasscodeViewModel: LockEnterPasscodeViewModel) {
self.lockEnterPasscodeViewModel = lockEnterPasscodeViewModel
super.init(model: lockEnterPasscodeViewModel)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
lockView.lockTitle.text = lockEnterPasscodeViewModel?.initialLabelText lockView.lockTitle.text = lockEnterPasscodeViewModel.initialLabelText
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
//If max attempt limit is reached we should validate if one minute gone. //If max attempt limit is reached we should validate if one minute gone.
if lock.isIncorrectMaxAttemptTimeSet { if lockEnterPasscodeViewModel.lock.isIncorrectMaxAttemptTimeSet {
lockView.lockTitle.text = lockEnterPasscodeViewModel?.tryAfterOneMinute lockView.lockTitle.text = lockEnterPasscodeViewModel.tryAfterOneMinute
maxAttemptTimerValidation() maxAttemptTimerValidation()
} }
} }
func showBioMetricAuth() { func showBioMetricAuth() {
context = LAContext() lockEnterPasscodeViewModel.invalidateContext()
touchValidation() touchValidation()
} }
override func enteredPasscode(_ passcode: String) { override func enteredPasscode(_ passcode: String) {
super.enteredPasscode(passcode) super.enteredPasscode(passcode)
if lock.isPasscodeValid(passcode: passcode) { if lockEnterPasscodeViewModel.lock.isPasscodeValid(passcode: passcode) {
lock.resetPasscodeAttemptHistory() lockEnterPasscodeViewModel.lock.resetPasscodeAttemptHistory()
lock.removeIncorrectMaxAttemptTime() lockEnterPasscodeViewModel.lock.removeIncorrectMaxAttemptTime()
lockView.lockTitle.text = lockEnterPasscodeViewModel?.initialLabelText lockView.lockTitle.text = lockEnterPasscodeViewModel.initialLabelText
unlock(withResult: true, bioUnlock: false) unlock(withResult: true, bioUnlock: false)
} else { } else {
let numberOfAttempts = lock.numberOfAttempts let numberOfAttempts = lockEnterPasscodeViewModel.lock.numberOfAttempts
let passcodeAttemptLimit = model.passcodeAttemptLimit let passcodeAttemptLimit = lockEnterPasscodeViewModel.passcodeAttemptLimit
let text = R.string.localizable.lockEnterPasscodeViewModelIncorrectPasscode(passcodeAttemptLimit - numberOfAttempts) let text = R.string.localizable.lockEnterPasscodeViewModelIncorrectPasscode(passcodeAttemptLimit - numberOfAttempts)
lockView.lockTitle.text = text lockView.lockTitle.text = text
lockView.shake() lockView.shake()
@ -49,21 +52,21 @@ class LockEnterPasscodeViewController: LockPasscodeViewController {
exceededLimit() exceededLimit()
return return
} }
lock.recordIncorrectPasscodeAttempt() lockEnterPasscodeViewModel.lock.recordIncorrectPasscodeAttempt()
} }
} }
private func exceededLimit() { private func exceededLimit() {
lockView.lockTitle.text = lockEnterPasscodeViewModel?.tryAfterOneMinute lockView.lockTitle.text = lockEnterPasscodeViewModel.tryAfterOneMinute
lock.recordIncorrectMaxAttemptTime() lockEnterPasscodeViewModel.lock.recordIncorrectMaxAttemptTime()
hideKeyboard() hideKeyboard()
} }
private func maxAttemptTimerValidation() { private func maxAttemptTimerValidation() {
let now = Date() let now = Date()
let maxAttemptTimer = lock.recordedMaxAttemptTime let maxAttemptTimer = lockEnterPasscodeViewModel.lock.recordedMaxAttemptTime
let interval = now.timeIntervalSince(maxAttemptTimer) let interval = now.timeIntervalSince(maxAttemptTimer)
//if interval is greater or equal 60 seconds we give 1 attempt. //if interval is greater or equal 60 seconds we give 1 attempt.
if interval >= 60 { if interval >= 60 {
lockView.lockTitle.text = lockEnterPasscodeViewModel?.initialLabelText lockView.lockTitle.text = lockEnterPasscodeViewModel.initialLabelText
showKeyboard() showKeyboard()
} }
} }
@ -75,21 +78,17 @@ class LockEnterPasscodeViewController: LockPasscodeViewController {
} }
private func touchValidation() { private func touchValidation() {
guard canEvaluatePolicy, let reason = lockEnterPasscodeViewModel?.loginReason else { guard lockEnterPasscodeViewModel.canEvaluatePolicy else { return }
return
}
hideKeyboard() hideKeyboard()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { [weak self] success, _ in lockEnterPasscodeViewModel.evaluatePolicy() { [weak self] success in
DispatchQueue.main.async { if success {
if success { self?.lockEnterPasscodeViewModel.lock.resetPasscodeAttemptHistory()
self?.lock.resetPasscodeAttemptHistory() self?.lockEnterPasscodeViewModel.lock.removeIncorrectMaxAttemptTime()
self?.lock.removeIncorrectMaxAttemptTime() self?.lockView.lockTitle.text = self?.lockEnterPasscodeViewModel.initialLabelText
self?.lockView.lockTitle.text = self?.lockEnterPasscodeViewModel?.initialLabelText self?.unlock(withResult: true, bioUnlock: true)
self?.unlock(withResult: true, bioUnlock: true) } else {
} else { self?.showKeyboard()
self?.showKeyboard() }
}
}
} }
} }
} }

@ -6,9 +6,9 @@ import AlphaWalletFoundation
class LockPasscodeViewController: UIViewController { class LockPasscodeViewController: UIViewController {
var willFinishWithResult: ((_ success: Bool) -> Void)? var willFinishWithResult: ((_ success: Bool) -> Void)?
let model: LockViewModel private let model: LockViewModel
var lockView: LockView! var lockView: LockView!
let lock = Lock()
private var invisiblePasscodeField = UITextField() private var invisiblePasscodeField = UITextField()
private var shouldIgnoreTextFieldDelegateCalls = false private var shouldIgnoreTextFieldDelegateCalls = false
init(model: LockViewModel) { init(model: LockViewModel) {
@ -25,7 +25,7 @@ class LockPasscodeViewController: UIViewController {
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
if !invisiblePasscodeField.isFirstResponder && !lock.isIncorrectMaxAttemptTimeSet { if !invisiblePasscodeField.isFirstResponder && !model.isIncorrectMaxAttemptTimeSet {
invisiblePasscodeField.becomeFirstResponder() invisiblePasscodeField.becomeFirstResponder()
} }
} }

@ -6,4 +6,10 @@ class LockCreatePasscodeViewModel: LockViewModel {
let title = R.string.localizable.lockCreatePasscodeViewModelTitle() let title = R.string.localizable.lockCreatePasscodeViewModelTitle()
let initialLabelText = R.string.localizable.lockCreatePasscodeViewModelInitial() let initialLabelText = R.string.localizable.lockCreatePasscodeViewModelInitial()
let confirmLabelText = R.string.localizable.lockCreatePasscodeViewModelConfirm() let confirmLabelText = R.string.localizable.lockCreatePasscodeViewModelConfirm()
private (set) var firstPasscode: String?
func set(firstPasscode: String?) {
self.firstPasscode = firstPasscode
}
} }

@ -1,9 +1,27 @@
// Copyright SIX DAY LLC. All rights reserved. // Copyright SIX DAY LLC. All rights reserved.
import Foundation import Foundation
import LocalAuthentication
class LockEnterPasscodeViewModel: LockViewModel { class LockEnterPasscodeViewModel: LockViewModel {
let initialLabelText = R.string.localizable.lockEnterPasscodeViewModelInitial() let initialLabelText = R.string.localizable.lockEnterPasscodeViewModelInitial()
let tryAfterOneMinute = R.string.localizable.lockEnterPasscodeViewModelTryAfterOneMinute() let tryAfterOneMinute = R.string.localizable.lockEnterPasscodeViewModelTryAfterOneMinute()
let loginReason = R.string.localizable.lockEnterPasscodeViewModelTouchId() let loginReason = R.string.localizable.lockEnterPasscodeViewModelTouchId()
private var context: LAContext!
var canEvaluatePolicy: Bool {
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
}
func invalidateContext() {
context = LAContext()
}
func evaluatePolicy(completion: @escaping (Bool) -> Void) {
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: loginReason) { success, _ in
DispatchQueue.main.async {
completion(success)
}
}
}
} }

@ -4,7 +4,12 @@ import Foundation
import AlphaWalletFoundation import AlphaWalletFoundation
class LockViewModel { class LockViewModel {
private let lock = Lock() let lock: Lock
init(lock: Lock) {
self.lock = lock
}
var charCount: Int { var charCount: Int {
//This step is required for old clients to support 4 digit passcode. //This step is required for old clients to support 4 digit passcode.
var count = 0 var count = 0
@ -15,6 +20,9 @@ class LockViewModel {
} }
return count return count
} }
var isIncorrectMaxAttemptTimeSet: Bool {
lock.isIncorrectMaxAttemptTimeSet
}
var passcodeAttemptLimit: Int { var passcodeAttemptLimit: Int {
//If max attempt limit is reached we should give only 1 attempt. //If max attempt limit is reached we should give only 1 attempt.
return lock.isIncorrectMaxAttemptTimeSet ? 1 : 5 return lock.isIncorrectMaxAttemptTimeSet ? 1 : 5

@ -7,15 +7,16 @@ import AlphaWalletFoundation
class LockEnterPasscodeCoordinator: Coordinator { class LockEnterPasscodeCoordinator: Coordinator {
var coordinators: [Coordinator] = [] var coordinators: [Coordinator] = []
let window: UIWindow = UIWindow() let window: UIWindow = UIWindow()
private let model: LockEnterPasscodeViewModel private let lock: Lock
private let lock: LockInterface
private lazy var lockEnterPasscodeViewController: LockEnterPasscodeViewController = { private lazy var lockEnterPasscodeViewController: LockEnterPasscodeViewController = {
return LockEnterPasscodeViewController(model: model) let viewModel = LockEnterPasscodeViewModel(lock: lock)
return LockEnterPasscodeViewController(lockEnterPasscodeViewModel: viewModel)
}() }()
init(model: LockEnterPasscodeViewModel, lock: LockInterface = Lock()) {
init(lock: Lock) {
self.lock = lock
self.window.windowLevel = UIWindow.Level.statusBar + 1.0 self.window.windowLevel = UIWindow.Level.statusBar + 1.0
self.model = model
self.lock = lock
lockEnterPasscodeViewController.unlockWithResult = { [weak self] (state, bioUnlock) in lockEnterPasscodeViewController.unlockWithResult = { [weak self] (state, bioUnlock) in
if state { if state {
self?.stop() self?.stop()
@ -23,7 +24,7 @@ class LockEnterPasscodeCoordinator: Coordinator {
} }
} }
func start() { func start() {
guard lock.isPasscodeSet else { return } guard lock.isPasscodeSet else { return }
window.rootViewController = lockEnterPasscodeViewController window.rootViewController = lockEnterPasscodeViewController
window.makeKeyAndVisible() window.makeKeyAndVisible()
} }
@ -32,7 +33,7 @@ class LockEnterPasscodeCoordinator: Coordinator {
} }
func showAuthentication() { func showAuthentication() {
guard lock.isPasscodeSet else { return } guard lock.isPasscodeSet else { return }
lockEnterPasscodeViewController.showKeyboard() lockEnterPasscodeViewController.showKeyboard()
lockEnterPasscodeViewController.showBioMetricAuth() lockEnterPasscodeViewController.showBioMetricAuth()
} }

@ -10,14 +10,15 @@ class ProtectionCoordinator: Coordinator {
}() }()
private lazy var lockEnterPasscodeCoordinator: LockEnterPasscodeCoordinator = { private lazy var lockEnterPasscodeCoordinator: LockEnterPasscodeCoordinator = {
return LockEnterPasscodeCoordinator(model: LockEnterPasscodeViewModel()) return LockEnterPasscodeCoordinator(lock: lock)
}() }()
private let protectionWindow = UIWindow() private let protectionWindow = UIWindow()
private let lock: Lock
var coordinators: [Coordinator] = [] var coordinators: [Coordinator] = []
init() { init(lock: Lock) {
self.lock = lock
protectionWindow.windowLevel = UIWindow.Level.statusBar + 2.0 protectionWindow.windowLevel = UIWindow.Level.statusBar + 2.0
} }

@ -35,6 +35,7 @@ class SettingsCoordinator: Coordinator {
private var account: Wallet { private var account: Wallet {
return sessions.anyValue.account return sessions.anyValue.account
} }
private let lock: Lock
weak private var advancedSettingsViewController: AdvancedSettingsViewController? weak private var advancedSettingsViewController: AdvancedSettingsViewController?
let navigationController: UINavigationController let navigationController: UINavigationController
@ -42,7 +43,7 @@ class SettingsCoordinator: Coordinator {
var coordinators: [Coordinator] = [] var coordinators: [Coordinator] = []
lazy var rootViewController: SettingsViewController = { lazy var rootViewController: SettingsViewController = {
let viewModel = SettingsViewModel(account: account, keystore: keystore, config: config, analytics: analytics, domainResolutionService: domainResolutionService) let viewModel = SettingsViewModel(account: account, keystore: keystore, lock: lock, config: config, analytics: analytics, domainResolutionService: domainResolutionService)
let controller = SettingsViewController(viewModel: viewModel) let controller = SettingsViewController(viewModel: viewModel)
controller.delegate = self controller.delegate = self
return controller return controller
@ -60,10 +61,11 @@ class SettingsCoordinator: Coordinator {
walletBalanceService: WalletBalanceService, walletBalanceService: WalletBalanceService,
blockscanChatService: BlockscanChatService, blockscanChatService: BlockscanChatService,
blockiesGenerator: BlockiesGenerator, blockiesGenerator: BlockiesGenerator,
domainResolutionService: DomainResolutionServiceType domainResolutionService: DomainResolutionServiceType,
lock: Lock
) { ) {
self.navigationController = navigationController self.navigationController = navigationController
self.lock = lock
self.keystore = keystore self.keystore = keystore
self.config = config self.config = config
self.sessions = sessions self.sessions = sessions

@ -137,8 +137,7 @@ class SettingsViewController: UIViewController {
private func askUserToSetPasscode(completion: ((Bool) -> Void)? = .none) { private func askUserToSetPasscode(completion: ((Bool) -> Void)? = .none) {
guard let navigationController = navigationController else { return } guard let navigationController = navigationController else { return }
let viewModel = LockCreatePasscodeViewModel() let lock = LockCreatePasscodeCoordinator(navigationController: navigationController, lock: viewModel.lock)
let lock = LockCreatePasscodeCoordinator(navigationController: navigationController, model: viewModel)
lock.start() lock.start()
lock.lockViewController.willFinishWithResult = { result in lock.lockViewController.willFinishWithResult = { result in
completion?(result) completion?(result)

@ -23,12 +23,11 @@ final class SettingsViewModel {
private (set) var sections: [SettingsSection] = [] private (set) var sections: [SettingsSection] = []
private var assignedNameOrEns: String? private var assignedNameOrEns: String?
private let lock = Lock() let lock: Lock
private var config: Config private var config: Config
private let keystore: Keystore private let keystore: Keystore
private let analytics: AnalyticsLogger private let analytics: AnalyticsLogger
private let getWalletName: GetWalletName private let getWalletName: GetWalletName
let animatingDifferences: Bool = false let animatingDifferences: Bool = false
var title: String = R.string.localizable.aSettingsNavigationTitle() var title: String = R.string.localizable.aSettingsNavigationTitle()
var backgroundColor: UIColor = GroupedTable.Color.background var backgroundColor: UIColor = GroupedTable.Color.background
@ -43,11 +42,12 @@ final class SettingsViewModel {
} }
} }
init(account: Wallet, keystore: Keystore, config: Config, analytics: AnalyticsLogger, domainResolutionService: DomainResolutionServiceType) { init(account: Wallet, keystore: Keystore, lock: Lock, config: Config, analytics: AnalyticsLogger, domainResolutionService: DomainResolutionServiceType) {
self.account = account self.account = account
self.config = config self.config = config
self.keystore = keystore self.keystore = keystore
self.analytics = analytics self.analytics = analytics
self.lock = lock
self.getWalletName = GetWalletName(domainResolutionService: domainResolutionService) self.getWalletName = GetWalletName(domainResolutionService: domainResolutionService)
} }

@ -41,7 +41,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline, tokenCollection: dep.pipeline,
importToken: dep.importToken, importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore, transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService) tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false) coordinator.start(animated: false)
@ -108,7 +109,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep1.pipeline, tokenCollection: dep1.pipeline,
importToken: dep1.importToken, importToken: dep1.importToken,
transactionsDataStore: dep1.transactionsDataStore, transactionsDataStore: dep1.transactionsDataStore,
tokensService: dep1.tokensService) tokensService: dep1.tokensService,
lock: FakeLock())
c1.start(animated: false) c1.start(animated: false)
@ -141,7 +143,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep2.pipeline, tokenCollection: dep2.pipeline,
importToken: dep2.importToken, importToken: dep2.importToken,
transactionsDataStore: dep2.transactionsDataStore, transactionsDataStore: dep2.transactionsDataStore,
tokensService: dep2.tokensService) tokensService: dep2.tokensService,
lock: FakeLock())
c1.start(animated: false) c1.start(animated: false)
@ -183,7 +186,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline, tokenCollection: dep.pipeline,
importToken: dep.importToken, importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore, transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService) tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false) coordinator.start(animated: false)
coordinator.showPaymentFlow(for: .send(type: .transaction(TransactionType.nativeCryptocurrency(Token(), destination: .none, amount: nil))), server: .main, navigationController: coordinator.navigationController) coordinator.showPaymentFlow(for: .send(type: .transaction(TransactionType.nativeCryptocurrency(Token(), destination: .none, amount: nil))), server: .main, navigationController: coordinator.navigationController)
@ -226,7 +230,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline, tokenCollection: dep.pipeline,
importToken: dep.importToken, importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore, transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService) tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false) coordinator.start(animated: false)
coordinator.showPaymentFlow(for: .request, server: .main, navigationController: coordinator.navigationController) coordinator.showPaymentFlow(for: .request, server: .main, navigationController: coordinator.navigationController)
@ -269,7 +274,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline, tokenCollection: dep.pipeline,
importToken: dep.importToken, importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore, transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService) tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false) coordinator.start(animated: false)
let viewController = (coordinator.tabBarController.selectedViewController as? UINavigationController)?.viewControllers[0] let viewController = (coordinator.tabBarController.selectedViewController as? UINavigationController)?.viewControllers[0]
@ -331,7 +337,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline, tokenCollection: dep.pipeline,
importToken: dep.importToken, importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore, transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService) tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false) coordinator.start(animated: false)

@ -4,6 +4,13 @@ import XCTest
@testable import AlphaWallet @testable import AlphaWallet
import AlphaWalletFoundation import AlphaWalletFoundation
extension KeychainStorage {
static func make() -> KeychainStorage {
let uniqueString = NSUUID().uuidString
return try! .init(keyPrefix: "fake" + uniqueString)
}
}
class AppCoordinatorTests: XCTestCase { class AppCoordinatorTests: XCTestCase {
func testStart() { func testStart() {
@ -12,7 +19,7 @@ class AppCoordinatorTests: XCTestCase {
window: UIWindow(), window: UIWindow(),
analytics: FakeAnalyticsService(), analytics: FakeAnalyticsService(),
keystore: FakeEtherKeystore(), keystore: FakeEtherKeystore(),
walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()])) walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]), securedStorage: KeychainStorage.make())
XCTAssertTrue(coordinator.navigationController.viewControllers[0].isSplashScreen) XCTAssertTrue(coordinator.navigationController.viewControllers[0].isSplashScreen)
coordinator.start() coordinator.start()
@ -32,7 +39,7 @@ class AppCoordinatorTests: XCTestCase {
recentlyUsedWallet: .make() recentlyUsedWallet: .make()
), ),
walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]), walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]),
navigationController: FakeNavigationController() navigationController: FakeNavigationController(), securedStorage: KeychainStorage.make()
) )
coordinator.start() coordinator.start()
@ -55,7 +62,7 @@ class AppCoordinatorTests: XCTestCase {
wallets: [.make()], wallets: [.make()],
recentlyUsedWallet: .make() recentlyUsedWallet: .make()
), ),
walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]) walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]), securedStorage: KeychainStorage.make()
) )
coordinator.start() coordinator.start()
coordinator.reset() coordinator.reset()
@ -76,7 +83,7 @@ class AppCoordinatorTests: XCTestCase {
recentlyUsedWallet: .make() recentlyUsedWallet: .make()
), ),
walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]), walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]),
navigationController: FakeNavigationController() navigationController: FakeNavigationController(), securedStorage: KeychainStorage.make()
) )
coordinator.start() coordinator.start()
coordinator.showInitialWalletCoordinator() coordinator.showInitialWalletCoordinator()
@ -97,7 +104,7 @@ class AppCoordinatorTests: XCTestCase {
recentlyUsedWallet: .make() recentlyUsedWallet: .make()
), ),
walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]), walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]),
navigationController: FakeNavigationController() navigationController: FakeNavigationController(), securedStorage: KeychainStorage.make()
) )
coordinator.start() coordinator.start()
@ -120,7 +127,7 @@ class AppCoordinatorTests: XCTestCase {
wallets: [.make()], wallets: [.make()],
recentlyUsedWallet: .make() recentlyUsedWallet: .make()
), ),
walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]) walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]), securedStorage: KeychainStorage.make()
) )
coordinator.start() coordinator.start()
@ -137,7 +144,7 @@ class AppCoordinatorTests: XCTestCase {
window: .init(), window: .init(),
analytics: FakeAnalyticsService(), analytics: FakeAnalyticsService(),
keystore: FakeEtherKeystore(), keystore: FakeEtherKeystore(),
walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()])) walletAddressesStore: fakeWalletAddressesStore(wallets: [.make()]), securedStorage: KeychainStorage.make())
coordinator.start() coordinator.start()

@ -7,13 +7,13 @@ import AlphaWalletFoundation
class LockCreatePasscodeCoordinatorTest: XCTestCase { class LockCreatePasscodeCoordinatorTest: XCTestCase {
func testStart() { func testStart() {
let navigationController = NavigationController() let navigationController = NavigationController()
let coordinator = LockCreatePasscodeCoordinator(navigationController: navigationController, model: LockCreatePasscodeViewModel()) let coordinator = LockCreatePasscodeCoordinator(navigationController: navigationController, lock: FakeLock())
coordinator.start() coordinator.start()
XCTAssertTrue(navigationController.viewControllers.first is LockCreatePasscodeViewController) XCTAssertTrue(navigationController.viewControllers.first is LockCreatePasscodeViewController)
} }
func testStop() { func testStop() {
let navigationController = NavigationController() let navigationController = NavigationController()
let coordinator = LockCreatePasscodeCoordinator(navigationController: navigationController, model: LockCreatePasscodeViewModel()) let coordinator = LockCreatePasscodeCoordinator(navigationController: navigationController, lock: FakeLock())
coordinator.start() coordinator.start()
XCTAssertTrue(navigationController.viewControllers.first is LockCreatePasscodeViewController) XCTAssertTrue(navigationController.viewControllers.first is LockCreatePasscodeViewController)
coordinator.stop() coordinator.stop()

@ -6,28 +6,25 @@ import AlphaWalletFoundation
class LockEnterPasscodeCoordinatorTest: XCTestCase { class LockEnterPasscodeCoordinatorTest: XCTestCase {
func testStart() { func testStart() {
let viewModel = LockEnterPasscodeViewModel() let fakeLock = FakeLock()
let fakeLock = FakeLockProtocol() let coordinator = LockEnterPasscodeCoordinator(lock: fakeLock)
let coordinator = LockEnterPasscodeCoordinator(model: viewModel, lock: fakeLock)
XCTAssertTrue(coordinator.window.isHidden) XCTAssertTrue(coordinator.window.isHidden)
coordinator.start() coordinator.start()
XCTAssertFalse(coordinator.window.isHidden) XCTAssertFalse(coordinator.window.isHidden)
coordinator.stop() coordinator.stop()
} }
func testStop() { func testStop() {
let viewModel = LockEnterPasscodeViewModel() let fakeLock = FakeLock()
let fakeLock = FakeLockProtocol() let coordinator = LockEnterPasscodeCoordinator(lock: fakeLock)
let coordinator = LockEnterPasscodeCoordinator(model: viewModel, lock: fakeLock)
coordinator.start() coordinator.start()
XCTAssertFalse(coordinator.window.isHidden) XCTAssertFalse(coordinator.window.isHidden)
coordinator.stop() coordinator.stop()
XCTAssertTrue(coordinator.window.isHidden) XCTAssertTrue(coordinator.window.isHidden)
} }
func testDisableLock() { func testDisableLock() {
let viewModel = LockEnterPasscodeViewModel() let fakeLock = FakeLock()
let fakeLock = FakeLockProtocol()
fakeLock.passcodeSet = false fakeLock.passcodeSet = false
let coordinator = LockEnterPasscodeCoordinator(model: viewModel, lock: fakeLock) let coordinator = LockEnterPasscodeCoordinator(lock: fakeLock)
XCTAssertTrue(coordinator.window.isHidden) XCTAssertTrue(coordinator.window.isHidden)
coordinator.start() coordinator.start()
XCTAssertTrue(coordinator.window.isHidden) XCTAssertTrue(coordinator.window.isHidden)

@ -4,7 +4,6 @@ import XCTest
import LocalAuthentication import LocalAuthentication
@testable import AlphaWallet @testable import AlphaWallet
import BigInt import BigInt
import KeychainSwift
import AlphaWalletFoundation import AlphaWalletFoundation
class EtherKeystoreTests: XCTestCase { class EtherKeystoreTests: XCTestCase {
@ -23,7 +22,7 @@ class EtherKeystoreTests: XCTestCase {
} }
func testEmptyPassword() throws { func testEmptyPassword() throws {
let keystore = try LegacyFileBasedKeystore(keystore: FakeEtherKeystore()) let keystore = try LegacyFileBasedKeystore(securedStorage: KeychainStorage.make(), keystore: FakeEtherKeystore())
let password = keystore.getPassword(for: .make()) let password = keystore.getPassword(for: .make())
XCTAssertNil(password) XCTAssertNil(password)
} }
@ -150,7 +149,7 @@ class EtherKeystoreTests: XCTestCase {
func testConvertPrivateKeyToKeyStore() throws { func testConvertPrivateKeyToKeyStore() throws {
let passphrase = "MyHardPassword!" let passphrase = "MyHardPassword!"
let keystore = FakeEtherKeystore() let keystore = FakeEtherKeystore()
let result = (try! LegacyFileBasedKeystore(keystore: keystore)).convertPrivateKeyToKeystoreFile(privateKey: Data(hexString: TestKeyStore.testPrivateKey)!, passphrase: passphrase) let result = (try! LegacyFileBasedKeystore(securedStorage: KeychainStorage.make(), keystore: keystore)).convertPrivateKeyToKeystoreFile(privateKey: Data(hexString: TestKeyStore.testPrivateKey)!, passphrase: passphrase)
let dict = try result.dematerialize() let dict = try result.dematerialize()
keystore.importWallet(type: .keystore(string: dict.jsonString!, password: passphrase)) { result in keystore.importWallet(type: .keystore(string: dict.jsonString!, password: passphrase)) { result in
guard let wallet = try? result.dematerialize() else { guard let wallet = try? result.dematerialize() else {

@ -2,21 +2,17 @@
import Foundation import Foundation
@testable import AlphaWallet @testable import AlphaWallet
import KeychainSwift
import AlphaWalletFoundation import AlphaWalletFoundation
final class FakeEtherKeystore: EtherKeystore { final class FakeEtherKeystore: EtherKeystore {
convenience init(wallets: [Wallet] = [], recentlyUsedWallet: Wallet? = nil) { convenience init(wallets: [Wallet] = [], recentlyUsedWallet: Wallet? = nil) {
let uniqueString = NSUUID().uuidString
let walletAddressesStore = fakeWalletAddressStore(wallets: wallets, recentlyUsedWallet: recentlyUsedWallet) let walletAddressesStore = fakeWalletAddressStore(wallets: wallets, recentlyUsedWallet: recentlyUsedWallet)
self.init(keychain: KeychainStorage.make(), walletAddressesStore: walletAddressesStore, analytics: FakeAnalyticsService())
try! self.init(keychain: KeychainSwift(keyPrefix: "fake" + uniqueString), walletAddressesStore: walletAddressesStore, analytics: FakeAnalyticsService())
self.recentlyUsedWallet = recentlyUsedWallet self.recentlyUsedWallet = recentlyUsedWallet
} }
convenience init(walletAddressesStore: WalletAddressesStore) { convenience init(walletAddressesStore: WalletAddressesStore) {
let uniqueString = NSUUID().uuidString self.init(keychain: KeychainStorage.make(), walletAddressesStore: walletAddressesStore, analytics: FakeAnalyticsService())
try! self.init(keychain: KeychainSwift(keyPrefix: "fake" + uniqueString), walletAddressesStore: walletAddressesStore, analytics: FakeAnalyticsService())
self.recentlyUsedWallet = recentlyUsedWallet self.recentlyUsedWallet = recentlyUsedWallet
} }
} }

@ -4,10 +4,44 @@ import UIKit
@testable import AlphaWallet @testable import AlphaWallet
import AlphaWalletFoundation import AlphaWalletFoundation
class FakeLockProtocol: LockInterface { class FakeLock: Lock {
var passcodeSet = true var passcodeSet = true
var isPasscodeSet: Bool { var isPasscodeSet: Bool { return passcodeSet }
return passcodeSet var currentPasscode: String? { return "passcode" }
var numberOfAttempts: Int { return 0 }
var recordedMaxAttemptTime: Date { return Date() }
var isIncorrectMaxAttemptTimeSet: Bool { return false }
func isPasscodeValid(passcode: String) -> Bool {
return false
}
func setPasscode(passcode: String) {
}
func deletePasscode() {
}
func resetPasscodeAttemptHistory() {
}
func recordIncorrectPasscodeAttempt() {
}
func recordIncorrectMaxAttemptTime() {
}
func removeIncorrectMaxAttemptTime() {
}
func clear() {
} }
} }

@ -30,7 +30,6 @@ PODS:
- EthereumABI - EthereumABI
- JSONRPCKit (~> 2.0.0) - JSONRPCKit (~> 2.0.0)
- Kanna - Kanna
- KeychainSwift
- Kingfisher (~> 7.0) - Kingfisher (~> 7.0)
- MailchimpSDK - MailchimpSDK
- Mixpanel-swift - Mixpanel-swift
@ -39,7 +38,6 @@ PODS:
- PromiseKit/Alamofire - PromiseKit/Alamofire
- PromiseKit/CorePromise - PromiseKit/CorePromise
- RealmSwift (= 10.27.0) - RealmSwift (= 10.27.0)
- SAMKeychain
- SwiftLint (= 0.40.3) - SwiftLint (= 0.40.3)
- SwiftyJSON (= 5.0.0) - SwiftyJSON (= 5.0.0)
- TrezorCrypto - TrezorCrypto
@ -322,7 +320,7 @@ SPEC CHECKSUMS:
AlphaWalletAddress: 62a69f5ccc7d6d17abf878c4274f4c3d5fdc97ba AlphaWalletAddress: 62a69f5ccc7d6d17abf878c4274f4c3d5fdc97ba
AlphaWalletCore: 451c8a4dfdba1d3b59a3ac47bf0dcabcf1f4c049 AlphaWalletCore: 451c8a4dfdba1d3b59a3ac47bf0dcabcf1f4c049
AlphaWalletENS: a93a871ad5d8a8c78cd87b60eb59fccf4b23e841 AlphaWalletENS: a93a871ad5d8a8c78cd87b60eb59fccf4b23e841
AlphaWalletFoundation: c23b1174b25afd91e7fd2ab92e1dd3fe087697b8 AlphaWalletFoundation: 3820457e487bed6aef79f8ee9319bff95307cd90
AlphaWalletGoBack: 935efdbd98fa80039f2a350cde5b3a50cea46564 AlphaWalletGoBack: 935efdbd98fa80039f2a350cde5b3a50cea46564
AlphaWalletOpenSea: 11ccb06ae0200dadc2b3f7c3e223f407ac3a22b4 AlphaWalletOpenSea: 11ccb06ae0200dadc2b3f7c3e223f407ac3a22b4
AlphaWalletWeb3Provider: 7ca1e1c1dc841dc1915f970daace48bf34931655 AlphaWalletWeb3Provider: 7ca1e1c1dc841dc1915f970daace48bf34931655

@ -24,7 +24,7 @@ Pod::Spec.new do |spec|
spec.dependency 'BigInt', '~> 3.1' spec.dependency 'BigInt', '~> 3.1'
spec.dependency 'JSONRPCKit', '~> 2.0.0' spec.dependency 'JSONRPCKit', '~> 2.0.0'
spec.dependency 'APIKit', '5.1.0' spec.dependency 'APIKit', '5.1.0'
spec.dependency 'KeychainSwift' # spec.dependency 'KeychainSwift'
spec.dependency 'SwiftLint', '0.40.3' spec.dependency 'SwiftLint', '0.40.3'
spec.dependency 'RealmSwift', '10.27.0' spec.dependency 'RealmSwift', '10.27.0'
spec.dependency 'Moya', '~> 10.0.1' spec.dependency 'Moya', '~> 10.0.1'
@ -35,7 +35,7 @@ Pod::Spec.new do |spec|
spec.dependency 'TrustKeystore' spec.dependency 'TrustKeystore'
spec.dependency 'SwiftyJSON', '5.0.0' spec.dependency 'SwiftyJSON', '5.0.0'
spec.dependency 'web3swift' spec.dependency 'web3swift'
spec.dependency 'SAMKeychain' # spec.dependency 'SAMKeychain'
spec.dependency 'PromiseKit/CorePromise' spec.dependency 'PromiseKit/CorePromise'
spec.dependency 'PromiseKit/Alamofire' spec.dependency 'PromiseKit/Alamofire'
spec.dependency "Kanna" spec.dependency "Kanna"

@ -8,11 +8,12 @@
import Foundation import Foundation
public final class CleanupPasscode: Initializer { public final class CleanupPasscode: Initializer {
private let lock = Lock() private let lock: Lock// = Lock(securedStorage: SecuredPasswordStorage & SecuredStorage)
private let keystore: Keystore private let keystore: Keystore
public init(keystore: Keystore) { public init(keystore: Keystore, lock: Lock) {
self.keystore = keystore self.keystore = keystore
self.lock = lock
} }
public func perform() { public func perform() {

@ -3,7 +3,6 @@
import Foundation import Foundation
import LocalAuthentication import LocalAuthentication
import BigInt import BigInt
import KeychainSwift
import WalletCore import WalletCore
import web3swift import web3swift
@ -24,6 +23,33 @@ extension UserDefaults {
} }
} }
public enum AccessOptions {
case accessibleWhenUnlocked
case accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: Bool)
case accessibleAfterFirstUnlock
case accessibleAfterFirstUnlockThisDeviceOnly
case accessibleAlways
case accessibleWhenPasscodeSetThisDeviceOnly
case accessibleAlwaysThisDeviceOnly
}
public protocol SecuredPasswordStorage {
func password(forService service: String, account: String) -> String?
func setPasword(_ pasword: String, forService service: String, account: String)
func deletePasword(forService service: String, account: String)
}
public protocol SecuredStorage {
var hasUserCancelledLastAccess: Bool { get }
var isDataNotFoundForLastAccess: Bool { get }
func set(_ value: String, forKey key: String, withAccess access: AccessOptions?) -> Bool
func set(_ value: Data, forKey key: String, withAccess access: AccessOptions?) -> Bool
func get(_ key: String, prompt: String?, withContext context: LAContext?) -> String?
func getData(_ key: String, prompt: String?, withContext context: LAContext?) -> Data?
func delete(_ key: String) -> Bool
}
// swiftlint:disable type_body_length // swiftlint:disable type_body_length
///We use ECDSA keys (created and stored in the Secure Enclave), achieving symmetric encryption based on Diffie-Hellman to encrypt the HD wallet seed (actually entropy) and raw private keys and store the ciphertext in the keychain. ///We use ECDSA keys (created and stored in the Secure Enclave), achieving symmetric encryption based on Diffie-Hellman to encrypt the HD wallet seed (actually entropy) and raw private keys and store the ciphertext in the keychain.
/// ///
@ -57,9 +83,9 @@ open class EtherKeystore: NSObject, Keystore {
} }
private let emptyPassphrase = "" private let emptyPassphrase = ""
private let keychain: KeychainSwift private let keychain: SecuredStorage
private let defaultKeychainAccessUserPresenceRequired: KeychainSwiftAccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: true) private let defaultKeychainAccessUserPresenceRequired: AccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: true)
private let defaultKeychainAccessUserPresenceNotRequired: KeychainSwiftAccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: false) private let defaultKeychainAccessUserPresenceNotRequired: AccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: false)
private var walletAddressesStore: WalletAddressesStore private var walletAddressesStore: WalletAddressesStore
private var analytics: AnalyticsLogger private var analytics: AnalyticsLogger
@ -108,12 +134,8 @@ open class EtherKeystore: NSObject, Keystore {
weak public var delegate: KeystoreDelegate? weak public var delegate: KeystoreDelegate?
public init(keychain: KeychainSwift = KeychainSwift(keyPrefix: Constants.keychainKeyPrefix), walletAddressesStore: WalletAddressesStore, analytics: AnalyticsLogger) throws { public init(keychain: SecuredStorage, walletAddressesStore: WalletAddressesStore, analytics: AnalyticsLogger) {
if !UIApplication.shared.isProtectedDataAvailable {
throw EtherKeystoreError.protectionDisabled
}
self.keychain = keychain self.keychain = keychain
self.keychain.synchronizable = false
self.analytics = analytics self.analytics = analytics
self.walletAddressesStore = walletAddressesStore self.walletAddressesStore = walletAddressesStore
super.init() super.init()
@ -160,7 +182,7 @@ open class EtherKeystore: NSObject, Keystore {
public func importWallet(type: ImportType) -> Result<Wallet, KeystoreError> { public func importWallet(type: ImportType) -> Result<Wallet, KeystoreError> {
switch type { switch type {
case .keystore(let json, let password): case .keystore(let json, let password):
guard let keystore = try? LegacyFileBasedKeystore(keystore: self) else { guard let keystore = try? LegacyFileBasedKeystore(securedStorage: keychain, keystore: self) else {
return .failure(.failedToExportPrivateKey) return .failure(.failedToExportPrivateKey)
} }
let result = keystore.getPrivateKeyFromKeystoreFile(json: json, password: password) let result = keystore.getPrivateKeyFromKeystoreFile(json: json, password: password)
@ -288,7 +310,7 @@ open class EtherKeystore: NSObject, Keystore {
return return
} }
//Careful to not replace the if-let with a flatMap(). Because the value is a Result and it has flatMap() defined to "resolve" only when it's .success //Careful to not replace the if-let with a flatMap(). Because the value is a Result and it has flatMap() defined to "resolve" only when it's .success
if let result = (try? LegacyFileBasedKeystore(keystore: self))?.export(privateKey: key, newPassword: newPassword) { if let result = (try? LegacyFileBasedKeystore(securedStorage: keychain, keystore: self))?.export(privateKey: key, newPassword: newPassword) {
completion(result) completion(result)
} else { } else {
completion(.failure(.failedToExportPrivateKey)) completion(.failure(.failedToExportPrivateKey))
@ -312,7 +334,7 @@ open class EtherKeystore: NSObject, Keystore {
return return
} }
//Careful to not replace the if-let with a flatMap(). Because the value is a Result and it has flatMap() defined to "resolve" only when it's .success //Careful to not replace the if-let with a flatMap(). Because the value is a Result and it has flatMap() defined to "resolve" only when it's .success
if let result = (try? LegacyFileBasedKeystore(keystore: self))?.export(privateKey: key, newPassword: newPassword) { if let result = (try? LegacyFileBasedKeystore(securedStorage: keychain, keystore: self))?.export(privateKey: key, newPassword: newPassword) {
completion(result) completion(result)
} else { } else {
completion(.failure(.failedToExportPrivateKey)) completion(.failure(.failedToExportPrivateKey))
@ -597,6 +619,7 @@ open class EtherKeystore: NSObject, Keystore {
if let data = data { if let data = data {
return .key(data) return .key(data)
} else { } else {
if keychain.hasUserCancelledLastAccess { if keychain.hasUserCancelledLastAccess {
return .userCancelled return .userCancelled
} else if keychain.isDataNotFoundForLastAccess { } else if keychain.isDataNotFoundForLastAccess {
@ -665,7 +688,7 @@ open class EtherKeystore: NSObject, Keystore {
private func savePrivateKeyForNonHdWallet(_ privateKey: Data, forAccount account: AlphaWallet.Address, withUserPresence: Bool) -> Bool { private func savePrivateKeyForNonHdWallet(_ privateKey: Data, forAccount account: AlphaWallet.Address, withUserPresence: Bool) -> Bool {
let context = createContext() let context = createContext()
guard let cipherTextData = encryptPrivateKey(privateKey, forAccount: account, withUserPresence: withUserPresence, withContext: context) else { return false } guard let cipherTextData = encryptPrivateKey(privateKey, forAccount: account, withUserPresence: withUserPresence, withContext: context) else { return false }
let access: KeychainSwiftAccessOptions let access: AccessOptions
let prefix: String let prefix: String
if withUserPresence { if withUserPresence {
access = defaultKeychainAccessUserPresenceRequired access = defaultKeychainAccessUserPresenceRequired
@ -680,7 +703,7 @@ open class EtherKeystore: NSObject, Keystore {
private func saveSeedForHdWallet(_ seed: String, forAccount account: AlphaWallet.Address, withUserPresence: Bool) -> Bool { private func saveSeedForHdWallet(_ seed: String, forAccount account: AlphaWallet.Address, withUserPresence: Bool) -> Bool {
let context = createContext() let context = createContext()
guard let cipherTextData = seed.data(using: .utf8).flatMap({ self.encryptHdWalletSeed($0, forAccount: account, withUserPresence: withUserPresence, withContext: context) }) else { return false } guard let cipherTextData = seed.data(using: .utf8).flatMap({ self.encryptHdWalletSeed($0, forAccount: account, withUserPresence: withUserPresence, withContext: context) }) else { return false }
let access: KeychainSwiftAccessOptions let access: AccessOptions
let prefix: String let prefix: String
if withUserPresence { if withUserPresence {
access = defaultKeychainAccessUserPresenceRequired access = defaultKeychainAccessUserPresenceRequired
@ -806,13 +829,3 @@ open class EtherKeystore: NSObject, Keystore {
} }
} }
// swiftlint:enable type_body_length // swiftlint:enable type_body_length
extension KeychainSwift {
var hasUserCancelledLastAccess: Bool {
return lastResultCode == errSecUserCanceled
}
var isDataNotFoundForLastAccess: Bool {
return lastResultCode == errSecItemNotFound
}
}

@ -2,29 +2,25 @@
import BigInt import BigInt
import Foundation import Foundation
import KeychainSwift
import CryptoSwift import CryptoSwift
import TrustKeystore import TrustKeystore
public enum FileBasedKeystoreError: LocalizedError { public enum FileBasedKeystoreError: LocalizedError {
case protectionDisabled case protectionDisabled
} }
fileprivate typealias LegacyKeyStore = TrustKeystore.KeyStore
public class LegacyFileBasedKeystore { public class LegacyFileBasedKeystore {
private let keychain: KeychainSwift private let securedStorage: SecuredStorage
private let datadir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] private let datadir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
private let keyStore: KeyStore private let keyStore: LegacyKeyStore
private let etherkeystore: Keystore private let etherkeystore: Keystore
let keystoreDirectory: URL let keystoreDirectory: URL
public init(keychain: KeychainSwift = KeychainSwift(keyPrefix: Constants.keychainKeyPrefix), keyStoreSubfolder: String = "/keystore", keystore: Keystore) throws { public init(securedStorage: SecuredStorage, keyStoreSubfolder: String = "/keystore", keystore: Keystore) throws {
if !UIApplication.shared.isProtectedDataAvailable {
throw FileBasedKeystoreError.protectionDisabled
}
self.keystoreDirectory = URL(fileURLWithPath: datadir + keyStoreSubfolder) self.keystoreDirectory = URL(fileURLWithPath: datadir + keyStoreSubfolder)
self.keychain = keychain self.securedStorage = securedStorage
self.keychain.synchronizable = false self.keyStore = try LegacyKeyStore(keydir: keystoreDirectory)
self.keyStore = try KeyStore(keydir: keystoreDirectory)
self.etherkeystore = keystore self.etherkeystore = keystore
} }
@ -78,7 +74,7 @@ public class LegacyFileBasedKeystore {
public func getPassword(for account: AlphaWallet.Address) -> String? { public func getPassword(for account: AlphaWallet.Address) -> String? {
//This has to be lowercased due to legacy reasons it had been written to as lowercased() earlier //This has to be lowercased due to legacy reasons it had been written to as lowercased() earlier
return keychain.get(account.eip55String.lowercased()) return securedStorage.get(account.eip55String.lowercased(), prompt: nil, withContext: nil)
} }
public func getAccount(forAddress address: AlphaWallet.Address) -> Account? { public func getAccount(forAddress address: AlphaWallet.Address) -> Account? {

@ -1,14 +1,25 @@
// Copyright SIX DAY LLC. All rights reserved. // Copyright SIX DAY LLC. All rights reserved.
import Foundation import Foundation
import SAMKeychain
import KeychainSwift
public protocol LockInterface { public protocol Lock {
var isPasscodeSet: Bool { get } var isPasscodeSet: Bool { get }
var currentPasscode: String? { get }
var numberOfAttempts: Int { get }
var recordedMaxAttemptTime: Date { get }
var isIncorrectMaxAttemptTimeSet: Bool { get }
func isPasscodeValid(passcode: String) -> Bool
func setPasscode(passcode: String)
func deletePasscode()
func resetPasscodeAttemptHistory()
func recordIncorrectPasscodeAttempt()
func recordIncorrectMaxAttemptTime()
func removeIncorrectMaxAttemptTime()
func clear()
} }
public class Lock: LockInterface { open class SecuredLock: Lock {
private struct Keys { private struct Keys {
static let service = "alphawallet.lock" static let service = "alphawallet.lock"
static let account = "alphawallet.account" static let account = "alphawallet.account"
@ -16,27 +27,26 @@ public class Lock: LockInterface {
private let passcodeAttempts = "passcodeAttempts" private let passcodeAttempts = "passcodeAttempts"
private let maxAttemptTime = "maxAttemptTime" private let maxAttemptTime = "maxAttemptTime"
private let keychain = KeychainSwift(keyPrefix: Constants.keychainKeyPrefix) private let securedStorage: SecuredStorage & SecuredPasswordStorage
public var isPasscodeSet: Bool { public var isPasscodeSet: Bool {
return currentPasscode != nil return currentPasscode != nil
} }
public var currentPasscode: String? { public var currentPasscode: String? {
return SAMKeychain.password(forService: Keys.service, account: Keys.account) return securedStorage.password(forService: Keys.service, account: Keys.account)
} }
public var numberOfAttempts: Int { public var numberOfAttempts: Int {
guard let attempts = keychain.get(passcodeAttempts) else { guard let attempts = securedStorage.get(passcodeAttempts, prompt: nil, withContext: nil), let value = Int(attempts) else {
return 0 return 0
} }
return Int(attempts)! return value
} }
public var recordedMaxAttemptTime: Date { public var recordedMaxAttemptTime: Date {
//This method is called only when we knew that maxAttemptTime is set. So no worries with !. //This method is called only when we knew that maxAttemptTime is set. So no worries with !.
let timeString = keychain.get(maxAttemptTime)! let timeString = securedStorage.get(maxAttemptTime, prompt: nil, withContext: nil)!
return dateFormatter.date(from: timeString)! return dateFormatter.date(from: timeString)!
} }
public var isIncorrectMaxAttemptTimeSet: Bool { public var isIncorrectMaxAttemptTimeSet: Bool {
guard let timeString = keychain.get(maxAttemptTime), !timeString.isEmpty else { guard let timeString = securedStorage.get(maxAttemptTime, prompt: nil, withContext: nil), !timeString.isEmpty else {
return false return false
} }
return true return true
@ -51,28 +61,31 @@ public class Lock: LockInterface {
return passcode == currentPasscode return passcode == currentPasscode
} }
public func setPasscode(passcode: String) { public func setPasscode(passcode: String) {
SAMKeychain.setPassword(passcode, forService: Keys.service, account: Keys.account) securedStorage.setPasword(passcode, forService: Keys.service, account: Keys.account)
} }
public func deletePasscode() { public func deletePasscode() {
SAMKeychain.deletePassword(forService: Keys.service, account: Keys.account) securedStorage.deletePasword(forService: Keys.service, account: Keys.account)
resetPasscodeAttemptHistory() resetPasscodeAttemptHistory()
} }
public func resetPasscodeAttemptHistory() { public func resetPasscodeAttemptHistory() {
keychain.delete(passcodeAttempts) securedStorage.delete(passcodeAttempts)
} }
public func recordIncorrectPasscodeAttempt() { public func recordIncorrectPasscodeAttempt() {
var numberOfAttemptsSoFar = numberOfAttempts var numberOfAttemptsSoFar = numberOfAttempts
numberOfAttemptsSoFar += 1 numberOfAttemptsSoFar += 1
keychain.set(String(numberOfAttemptsSoFar), forKey: passcodeAttempts) securedStorage.set(String(numberOfAttemptsSoFar), forKey: passcodeAttempts, withAccess: nil)
} }
public func recordIncorrectMaxAttemptTime() { public func recordIncorrectMaxAttemptTime() {
let timeString = dateFormatter.string(from: Date()) let timeString = dateFormatter.string(from: Date())
keychain.set(timeString, forKey: maxAttemptTime) securedStorage.set(timeString, forKey: maxAttemptTime, withAccess: nil)
} }
public func removeIncorrectMaxAttemptTime() { public func removeIncorrectMaxAttemptTime() {
keychain.delete(maxAttemptTime) securedStorage.delete(maxAttemptTime)
} }
public init() {} public init(securedStorage: SecuredStorage & SecuredPasswordStorage) {
self.securedStorage = securedStorage
}
public func clear() { public func clear() {
deletePasscode() deletePasscode()
resetPasscodeAttemptHistory() resetPasscodeAttemptHistory()

Loading…
Cancel
Save