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 */; };
87B1ACD928C0D45B0072A5E2 /* chains.zip in Resources */ = {isa = PBXBuildFile; fileRef = 87B1ACD828C0D45B0072A5E2 /* chains.zip */; };
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 */; };
87B93B0E2726A46500F6EA73 /* BrowserStorageSubscription.js in Resources */ = {isa = PBXBuildFile; fileRef = 87B93B0D2726A46500F6EA73 /* BrowserStorageSubscription.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>"; };
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; };
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>"; };
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>"; };
@ -3769,6 +3771,7 @@
026340902850C7D50033E51B /* Configuration.swift */,
5E7C7D674F6B2415FB5552B0 /* CanOpenContractWebPage.swift */,
875CA8DF28BDEE3D0020FA48 /* SwitchCustomChainCallbackId.swift */,
87B1AD3628C1FE030072A5E2 /* KeychainStorage.swift */,
);
path = Types;
sourceTree = "<group>";
@ -4609,6 +4612,7 @@
87BC890E26B7EDC6005482F4 /* WalletSummaryView.swift in Sources */,
8750F91924EE66D700E19DFF /* GasSpeedView.swift in Sources */,
875CA90728BE4EA20020FA48 /* UIDevice.swift in Sources */,
87B1AD3728C1FE030072A5E2 /* KeychainStorage.swift in Sources */,
295A59381F71C1B90092F0FC /* AccountsCoordinator.swift in Sources */,
87F15AF727F1EB7D00EB9787 /* SwapStepView.swift in Sources */,
879F185E26E74512000602F2 /* ButtonsBarBackgroundView.swift in Sources */,

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

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

@ -7,10 +7,6 @@ import AlphaWalletFoundation
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
var window: UIWindow?
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 = {
let provider = ReportProvider()
guard !isRunningTests() && isAlphaWallet() else { return provider }
@ -36,12 +32,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
let analytics = AnalyticsService()
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()
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
appCoordinator.start()
@ -54,7 +51,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
} catch {
}
protectionCoordinator.didFinishLaunchingWithOptions()
return true
}
@ -67,20 +63,19 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
}
func applicationWillResignActive(_ application: UIApplication) {
protectionCoordinator.applicationWillResignActive()
appCoordinator.applicationWillResignActive()
}
func applicationDidBecomeActive(_ application: UIApplication) {
protectionCoordinator.applicationDidBecomeActive()
appCoordinator.handleUniversalLinkInPasteboard()
appCoordinator.applicationDidBecomeActive()
}
func applicationDidEnterBackground(_ application: UIApplication) {
protectionCoordinator.applicationDidEnterBackground()
appCoordinator.applicationDidEnterBackground()
}
func applicationWillEnterForeground(_ application: UIApplication) {
protectionCoordinator.applicationWillEnterForeground()
appCoordinator.applicationWillEnterForeground()
}
func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool {
@ -111,9 +106,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
return handled
}
//TODO Handle SNS errors
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
//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
class LockCreatePasscodeCoordinator: Coordinator {
var coordinators: [Coordinator] = []
private let model: LockCreatePasscodeViewModel
private let lock: Lock
private let navigationController: UINavigationController
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.model = model
}
func start() {
lockViewController.navigationItem.largeTitleDisplayMode = .never

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

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

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

@ -6,4 +6,10 @@ class LockCreatePasscodeViewModel: LockViewModel {
let title = R.string.localizable.lockCreatePasscodeViewModelTitle()
let initialLabelText = R.string.localizable.lockCreatePasscodeViewModelInitial()
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.
import Foundation
import LocalAuthentication
class LockEnterPasscodeViewModel: LockViewModel {
let initialLabelText = R.string.localizable.lockEnterPasscodeViewModelInitial()
let tryAfterOneMinute = R.string.localizable.lockEnterPasscodeViewModelTryAfterOneMinute()
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
class LockViewModel {
private let lock = Lock()
let lock: Lock
init(lock: Lock) {
self.lock = lock
}
var charCount: Int {
//This step is required for old clients to support 4 digit passcode.
var count = 0
@ -15,6 +20,9 @@ class LockViewModel {
}
return count
}
var isIncorrectMaxAttemptTimeSet: Bool {
lock.isIncorrectMaxAttemptTimeSet
}
var passcodeAttemptLimit: Int {
//If max attempt limit is reached we should give only 1 attempt.
return lock.isIncorrectMaxAttemptTimeSet ? 1 : 5

@ -7,15 +7,16 @@ import AlphaWalletFoundation
class LockEnterPasscodeCoordinator: Coordinator {
var coordinators: [Coordinator] = []
let window: UIWindow = UIWindow()
private let model: LockEnterPasscodeViewModel
private let lock: LockInterface
private let lock: Lock
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.model = model
self.lock = lock
lockEnterPasscodeViewController.unlockWithResult = { [weak self] (state, bioUnlock) in
if state {
self?.stop()
@ -23,7 +24,7 @@ class LockEnterPasscodeCoordinator: Coordinator {
}
}
func start() {
guard lock.isPasscodeSet else { return }
guard lock.isPasscodeSet else { return }
window.rootViewController = lockEnterPasscodeViewController
window.makeKeyAndVisible()
}
@ -32,7 +33,7 @@ class LockEnterPasscodeCoordinator: Coordinator {
}
func showAuthentication() {
guard lock.isPasscodeSet else { return }
guard lock.isPasscodeSet else { return }
lockEnterPasscodeViewController.showKeyboard()
lockEnterPasscodeViewController.showBioMetricAuth()
}

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

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

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

@ -23,12 +23,11 @@ final class SettingsViewModel {
private (set) var sections: [SettingsSection] = []
private var assignedNameOrEns: String?
private let lock = Lock()
let lock: Lock
private var config: Config
private let keystore: Keystore
private let analytics: AnalyticsLogger
private let getWalletName: GetWalletName
let animatingDifferences: Bool = false
var title: String = R.string.localizable.aSettingsNavigationTitle()
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.config = config
self.keystore = keystore
self.analytics = analytics
self.lock = lock
self.getWalletName = GetWalletName(domainResolutionService: domainResolutionService)
}

@ -41,7 +41,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline,
importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService)
tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false)
@ -108,7 +109,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep1.pipeline,
importToken: dep1.importToken,
transactionsDataStore: dep1.transactionsDataStore,
tokensService: dep1.tokensService)
tokensService: dep1.tokensService,
lock: FakeLock())
c1.start(animated: false)
@ -141,7 +143,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep2.pipeline,
importToken: dep2.importToken,
transactionsDataStore: dep2.transactionsDataStore,
tokensService: dep2.tokensService)
tokensService: dep2.tokensService,
lock: FakeLock())
c1.start(animated: false)
@ -183,7 +186,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline,
importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService)
tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false)
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,
importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService)
tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false)
coordinator.showPaymentFlow(for: .request, server: .main, navigationController: coordinator.navigationController)
@ -269,7 +274,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline,
importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService)
tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false)
let viewController = (coordinator.tabBarController.selectedViewController as? UINavigationController)?.viewControllers[0]
@ -331,7 +337,8 @@ class ActiveWalletViewTests: XCTestCase {
tokenCollection: dep.pipeline,
importToken: dep.importToken,
transactionsDataStore: dep.transactionsDataStore,
tokensService: dep.tokensService)
tokensService: dep.tokensService,
lock: FakeLock())
coordinator.start(animated: false)

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

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

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

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

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

@ -4,10 +4,44 @@ import UIKit
@testable import AlphaWallet
import AlphaWalletFoundation
class FakeLockProtocol: LockInterface {
class FakeLock: Lock {
var passcodeSet = true
var isPasscodeSet: Bool {
return passcodeSet
var isPasscodeSet: Bool { 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
- JSONRPCKit (~> 2.0.0)
- Kanna
- KeychainSwift
- Kingfisher (~> 7.0)
- MailchimpSDK
- Mixpanel-swift
@ -39,7 +38,6 @@ PODS:
- PromiseKit/Alamofire
- PromiseKit/CorePromise
- RealmSwift (= 10.27.0)
- SAMKeychain
- SwiftLint (= 0.40.3)
- SwiftyJSON (= 5.0.0)
- TrezorCrypto
@ -322,7 +320,7 @@ SPEC CHECKSUMS:
AlphaWalletAddress: 62a69f5ccc7d6d17abf878c4274f4c3d5fdc97ba
AlphaWalletCore: 451c8a4dfdba1d3b59a3ac47bf0dcabcf1f4c049
AlphaWalletENS: a93a871ad5d8a8c78cd87b60eb59fccf4b23e841
AlphaWalletFoundation: c23b1174b25afd91e7fd2ab92e1dd3fe087697b8
AlphaWalletFoundation: 3820457e487bed6aef79f8ee9319bff95307cd90
AlphaWalletGoBack: 935efdbd98fa80039f2a350cde5b3a50cea46564
AlphaWalletOpenSea: 11ccb06ae0200dadc2b3f7c3e223f407ac3a22b4
AlphaWalletWeb3Provider: 7ca1e1c1dc841dc1915f970daace48bf34931655

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

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

@ -3,7 +3,6 @@
import Foundation
import LocalAuthentication
import BigInt
import KeychainSwift
import WalletCore
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
///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 keychain: KeychainSwift
private let defaultKeychainAccessUserPresenceRequired: KeychainSwiftAccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: true)
private let defaultKeychainAccessUserPresenceNotRequired: KeychainSwiftAccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: false)
private let keychain: SecuredStorage
private let defaultKeychainAccessUserPresenceRequired: AccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: true)
private let defaultKeychainAccessUserPresenceNotRequired: AccessOptions = .accessibleWhenUnlockedThisDeviceOnly(userPresenceRequired: false)
private var walletAddressesStore: WalletAddressesStore
private var analytics: AnalyticsLogger
@ -108,12 +134,8 @@ open class EtherKeystore: NSObject, Keystore {
weak public var delegate: KeystoreDelegate?
public init(keychain: KeychainSwift = KeychainSwift(keyPrefix: Constants.keychainKeyPrefix), walletAddressesStore: WalletAddressesStore, analytics: AnalyticsLogger) throws {
if !UIApplication.shared.isProtectedDataAvailable {
throw EtherKeystoreError.protectionDisabled
}
public init(keychain: SecuredStorage, walletAddressesStore: WalletAddressesStore, analytics: AnalyticsLogger) {
self.keychain = keychain
self.keychain.synchronizable = false
self.analytics = analytics
self.walletAddressesStore = walletAddressesStore
super.init()
@ -160,7 +182,7 @@ open class EtherKeystore: NSObject, Keystore {
public func importWallet(type: ImportType) -> Result<Wallet, KeystoreError> {
switch type {
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)
}
let result = keystore.getPrivateKeyFromKeystoreFile(json: json, password: password)
@ -288,7 +310,7 @@ open class EtherKeystore: NSObject, Keystore {
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
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)
} else {
completion(.failure(.failedToExportPrivateKey))
@ -312,7 +334,7 @@ open class EtherKeystore: NSObject, Keystore {
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
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)
} else {
completion(.failure(.failedToExportPrivateKey))
@ -597,6 +619,7 @@ open class EtherKeystore: NSObject, Keystore {
if let data = data {
return .key(data)
} else {
if keychain.hasUserCancelledLastAccess {
return .userCancelled
} 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 {
let context = createContext()
guard let cipherTextData = encryptPrivateKey(privateKey, forAccount: account, withUserPresence: withUserPresence, withContext: context) else { return false }
let access: KeychainSwiftAccessOptions
let access: AccessOptions
let prefix: String
if withUserPresence {
access = defaultKeychainAccessUserPresenceRequired
@ -680,7 +703,7 @@ open class EtherKeystore: NSObject, Keystore {
private func saveSeedForHdWallet(_ seed: String, forAccount account: AlphaWallet.Address, withUserPresence: Bool) -> Bool {
let context = createContext()
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
if withUserPresence {
access = defaultKeychainAccessUserPresenceRequired
@ -806,13 +829,3 @@ open class EtherKeystore: NSObject, Keystore {
}
}
// 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 Foundation
import KeychainSwift
import CryptoSwift
import TrustKeystore
public enum FileBasedKeystoreError: LocalizedError {
case protectionDisabled
}
fileprivate typealias LegacyKeyStore = TrustKeystore.KeyStore
public class LegacyFileBasedKeystore {
private let keychain: KeychainSwift
private let securedStorage: SecuredStorage
private let datadir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
private let keyStore: KeyStore
private let keyStore: LegacyKeyStore
private let etherkeystore: Keystore
let keystoreDirectory: URL
public init(keychain: KeychainSwift = KeychainSwift(keyPrefix: Constants.keychainKeyPrefix), keyStoreSubfolder: String = "/keystore", keystore: Keystore) throws {
if !UIApplication.shared.isProtectedDataAvailable {
throw FileBasedKeystoreError.protectionDisabled
}
public init(securedStorage: SecuredStorage, keyStoreSubfolder: String = "/keystore", keystore: Keystore) throws {
self.keystoreDirectory = URL(fileURLWithPath: datadir + keyStoreSubfolder)
self.keychain = keychain
self.keychain.synchronizable = false
self.keyStore = try KeyStore(keydir: keystoreDirectory)
self.securedStorage = securedStorage
self.keyStore = try LegacyKeyStore(keydir: keystoreDirectory)
self.etherkeystore = keystore
}
@ -78,7 +74,7 @@ public class LegacyFileBasedKeystore {
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
return keychain.get(account.eip55String.lowercased())
return securedStorage.get(account.eip55String.lowercased(), prompt: nil, withContext: nil)
}
public func getAccount(forAddress address: AlphaWallet.Address) -> Account? {

@ -1,14 +1,25 @@
// Copyright SIX DAY LLC. All rights reserved.
import Foundation
import SAMKeychain
import KeychainSwift
public protocol LockInterface {
public protocol Lock {
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 {
static let service = "alphawallet.lock"
static let account = "alphawallet.account"
@ -16,27 +27,26 @@ public class Lock: LockInterface {
private let passcodeAttempts = "passcodeAttempts"
private let maxAttemptTime = "maxAttemptTime"
private let keychain = KeychainSwift(keyPrefix: Constants.keychainKeyPrefix)
private let securedStorage: SecuredStorage & SecuredPasswordStorage
public var isPasscodeSet: Bool {
return currentPasscode != nil
}
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 {
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 Int(attempts)!
return value
}
public var recordedMaxAttemptTime: Date {
//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)!
}
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 true
@ -51,28 +61,31 @@ public class Lock: LockInterface {
return passcode == currentPasscode
}
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() {
SAMKeychain.deletePassword(forService: Keys.service, account: Keys.account)
securedStorage.deletePasword(forService: Keys.service, account: Keys.account)
resetPasscodeAttemptHistory()
}
public func resetPasscodeAttemptHistory() {
keychain.delete(passcodeAttempts)
securedStorage.delete(passcodeAttempts)
}
public func recordIncorrectPasscodeAttempt() {
var numberOfAttemptsSoFar = numberOfAttempts
numberOfAttemptsSoFar += 1
keychain.set(String(numberOfAttemptsSoFar), forKey: passcodeAttempts)
securedStorage.set(String(numberOfAttemptsSoFar), forKey: passcodeAttempts, withAccess: nil)
}
public func recordIncorrectMaxAttemptTime() {
let timeString = dateFormatter.string(from: Date())
keychain.set(timeString, forKey: maxAttemptTime)
securedStorage.set(timeString, forKey: maxAttemptTime, withAccess: nil)
}
public func removeIncorrectMaxAttemptTime() {
keychain.delete(maxAttemptTime)
securedStorage.delete(maxAttemptTime)
}
public init() {}
public init(securedStorage: SecuredStorage & SecuredPasswordStorage) {
self.securedStorage = securedStorage
}
public func clear() {
deletePasscode()
resetPasscodeAttemptHistory()

Loading…
Cancel
Save