MALINKA-1095: Password logic refactoring

This commit is contained in:
Андрей Геращенко
2023-03-10 11:40:54 +03:00
committed by Jura Shikin
parent 973e1290dc
commit f52cdb51bc
25 changed files with 818 additions and 234 deletions
@@ -0,0 +1,26 @@
//
// KeychainProtocol.swift
//
//
// Created by Nut.Tech on 16.02.2023.
//
import Foundation
public typealias Key = CommonKey
public protocol KeychainProtocol {
/// Checks if a given common key have a password-saved value
/// - Parameter key: Common Key
/// - Returns: is value exists
func exist(_ key: Key) -> Bool
/// Checks if a given common key have a biometric-saved value
/// - Parameter key: Common Key
/// - Returns: is value exists
func bioExist(_ key: Key) -> Bool
/// Access to common key value by biometric authenfication
subscript(biometric key: Key) -> String? { get }
/// Access to common key value by password authenfication
subscript(_ key: Key, password password: String) -> String? { get set }
}
@@ -5,18 +5,15 @@
// Created by Juraldinio on 11/27/22.
//
import Foundation
import LocalAuthentication
extension String {
fileprivate static let password = "PWD"
fileprivate static let biometric = "BIO"
}
final public class WalletKeychain: KeychainProtocol {
private enum KeychainLocals {
public static let password = "PWD"
public static let biometric = "BIO"
}
final public class WalletKeychain {
public typealias Key = CommonKey
public static let instance = WalletKeychain()
// MARK: - Init
@@ -25,17 +22,17 @@ final public class WalletKeychain {
// MARK: - Interface
public func exist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(.password)) }
public func exist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(KeychainLocals.password)) }
public func bioExist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(.biometric) ) }
public func bioExist(_ key: Key) -> Bool { self.checkProtectedExist(key: key.with(KeychainLocals.biometric) ) }
public subscript(biometric key: Key) -> String? {
self.loadBiometricProtected(key: key.with(.biometric))
self.loadBiometricProtected(key: key.with(KeychainLocals.biometric))
.map({ String(data: $0, encoding: .utf8) }) ?? nil
}
public subscript(_ key: Key, password password: String) -> String? {
get { loadPassProtected(key: key.with(.password), password: password).map { String(data: $0, encoding: .utf8) } ?? nil }
get { loadPassProtected(key: key.with(KeychainLocals.password), password: password).map { String(data: $0, encoding: .utf8) } ?? nil }
set { update(key, password: password, newValue: newValue) }
}
@@ -152,12 +149,13 @@ final public class WalletKeychain {
// MARK: -
private func update(_ key: Key, password: String, newValue: String?) {
//TODO: Refactor later - updation of biometric entry only when it is available and needed
if let value = newValue {
setPassProtected(key: key.with(.password), data: value, password: password)
setBiometricEntry(key: key.with(.biometric), data: value)
setPassProtected(key: key.with(KeychainLocals.password), data: value, password: password)
setBiometricEntry(key: key.with(KeychainLocals.biometric), data: value)
} else {
removeProtected(key: key.with(.password))
removeProtected(key: key.with(.biometric))
removeProtected(key: key.with(KeychainLocals.password))
removeProtected(key: key.with(KeychainLocals.biometric))
}
}
@@ -9,7 +9,17 @@ import Foundation
public extension Data {
func jsonDecoded<T: Decodable>(type: T.Type) -> T? { try? JSONDecoder().decode(type, from: self) }
func jsonDecoded<T: Decodable>(type: T.Type, userInfo: [CodingUserInfoKey: Any]? = nil) -> T? {
try? self.makeDecoder(userInfo: userInfo).decode(type, from: self)
}
func jsonDecoded<T: Decodable>(type: T.Type) -> [T]? { try? JSONDecoder().decode([T].self, from: self) }
func jsonDecoded<T: Decodable>(type: T.Type, userInfo: [CodingUserInfoKey: Any]? = nil) -> [T]? {
try? self.makeDecoder(userInfo: userInfo).decode([T].self, from: self)
}
private func makeDecoder(userInfo: [CodingUserInfoKey: Any]?) -> JSONDecoder {
let jsonDecoder = JSONDecoder()
userInfo >>- { jsonDecoder.userInfo = $0 }
return jsonDecoder
}
}
+3 -2
View File
@@ -16,14 +16,15 @@ let package = Package(
.package(name: "eosswift", path: "../../Vendors/spm/eos-swift"),
.package(name: "KeyChainAccess", path: "../../Vendors/spm/KeyChainAccess"),
.package(name: "WalletFoundation", path: "../WalletFoundation"),
.package(name: "WalletNetwork", path: "../WalletNetwork")
.package(name: "WalletNetwork", path: "../WalletNetwork"),
.package(name: "CryptoSwift", path: "../../Vendors/spm/CryptoSwift-1.5.1")
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "WalletKit",
dependencies: ["eosswift", "KeyChainAccess", "WalletFoundation", "WalletNetwork"],
dependencies: ["CryptoSwift", "eosswift", "KeyChainAccess", "WalletFoundation", "WalletNetwork"],
path: "./Sources"),
.testTarget(
name: "WalletKitTests",
@@ -9,16 +9,9 @@ import Foundation
import WalletFoundation
import WalletNetwork
public enum VillageError: Error {
case passwordNotMatch
}
final public class Village {
private enum Constants {
static let passwordKey = CommonKey("Account.Service.password")
}
private static var keychainKeeper = PrivacyKeeper(keychain: WalletKeychain.instance)
private static var villages = [Village]()
private let environment: NetworkEnvironment
@@ -41,6 +34,7 @@ final public class Village {
/// Create instance of Village.
public static func get(with environment: NetworkEnvironment) -> Village {
if let village = Self.villages.first(where: { $0.environment.isEquals(other: environment) }) {
return village
}
@@ -99,17 +93,19 @@ final public class Village {
}
/// Create bank that contain Wallets.
public func getBank(with password: String, on device: Device) -> Bank {
public func getBank(with password: String, on device: Device) throws -> Bank {
if let bank = self.houses.first(where: { $0.isEquals(password: password, device: device, environment: self.environment) }) {
guard Self.keychainKeeper.accept(password: password) else {
throw WalletError.passwordNotMatch
}
if let bank = self.houses.first(where: { $0.isEquals(device: device, environment: self.environment, password: password) }) {
return bank
}
if Self.password != password {
Self.password = password
}
let house = VillageHouse.create(for: password, on: device, environment: self.environment)
let house = VillageHouse.create(password: password,
keeper: Self.keychainKeeper,
on: device,
environment: self.environment)
self.houses.append(house)
return house
@@ -117,31 +113,24 @@ final public class Village {
public static func reset() {
VillageHouse.removeAllVillagers()
Self.password = nil
Self.keychainKeeper.reset()
}
public static var isPasswordExists: Bool { WalletKeychain.instance.exist(Constants.passwordKey) }
// Keychain static methods
public static func getPassword(password: String?) -> String? {
if let password {
return WalletKeychain.instance[Constants.passwordKey, password: password]
} else {
return WalletKeychain.instance[biometric: Constants.passwordKey]
}
public static func accept(password: String) -> Bool {
Self.keychainKeeper.accept(password: password)
}
// TODO: Remove this function after refactoring passwords
public static func updateCommonPassword(password: String, old: String) throws {
if WalletKeychain.instance[Constants.passwordKey, password: old] != nil {
Self.password = password
} else {
throw VillageError.passwordNotMatch
}
public static func passwordViaBiometrics() -> String? {
Self.keychainKeeper.passwordByBiometrics()
}
private static var password: String? {
get { WalletKeychain.instance[biometric: Constants.passwordKey] }
set { WalletKeychain.instance[Constants.passwordKey, password: newValue ?? ""] = newValue }
public static var isPasswordExists: Bool {
Self.keychainKeeper.isPasswordExists
}
public static func set(password: String, old: String? = nil) throws {
try Self.keychainKeeper.update(password: password, old: old)
}
}
@@ -0,0 +1,230 @@
//
// PrivacyKeeper.swift
//
//
// Created by Nut.Tech on 07.02.2023.
//
import WalletFoundation
import Foundation
import CryptoSwift
final class PrivacyKeeper {
private typealias CipherData = (key: String, iv: String)
private enum Constants {
static let oldPasswordKey = CommonKey("PrivacyKeeper.password")
static let passwordKey = "PrivacyKeeper.password"
static let cipherKey = "PrivacyKeeper.ckey"
static let ivKey = "PrivacyKeeper.ivkey"
}
var isPasswordExists: Bool {
if let encryptedPasswordKey {
return self.keychain.exist(encryptedPasswordKey)
}
return false
}
private var encryptedPasswordKey: CommonKey?
private var cipherData: CipherData
private var keychain: KeychainProtocol
init(keychain: KeychainProtocol) {
self.keychain = keychain
self.cipherData = Self.initCipherData()
self.encryptedPasswordKey = Self.makeEncryptedPasswordCommonKey(cipherData: self.cipherData)
}
/// Cheks if password correct
func accept(password: String) -> Bool {
self.getPassword(password) == password
}
/// Get password via biometry
func passwordByBiometrics() -> String? {
guard let encryptedPasswordKey else { return nil }
return self.keychain.bioExist(encryptedPasswordKey) ?
self.keychain[biometric: encryptedPasswordKey] : nil
}
/// Get private key from keychain
/// - Parameters:
/// - key: common key in keychain
/// - password: current password
/// - Returns: private key (if exists)
func privateKey(for key: String, password: String?) -> String? {
let pwd = password ?? self.passwordByBiometrics()
if let pwd {
return self.keychain[self.encryptPK(commonKey: key), password: pwd]
} else {
return nil
}
}
/// Update password to new one.
func update(password: String, old: String? = nil) throws {
guard self.isPasswordExists else {
try self.setPassword(password)
return
}
guard let old, self.accept(password: old) else {
throw WalletError.passwordNotMatch
}
try self.setPassword(password)
}
/// Update private key for current account. If you are using biometrics, pass nil in password.
/// - Parameters:
/// - privateKey: new private key
/// - key: common key in keychain
/// - password: current password
func update(privateKey: String?, for key: String, password: String?) {
let pwd = password ?? self.passwordByBiometrics()
guard let pwd, self.accept(password: pwd) else { return }
self.keychain[self.encryptPK(commonKey: key), password: pwd] = privateKey
}
/// Migrates pasword and private key storage in keychain to new style
/// - Parameters:
/// - oldKey: old private key common key for keychain
/// - newKey: new private key common key for keychain
/// - password: current password
func migrate(oldKey: CommonKey, newKey: CommonKey, password: String) {
self.migratePassword(password: password)
guard self.accept(password: password) else { return }
self.migratePrivateKey(oldKey: oldKey, newKey: newKey, password: password)
self.migrateToEncrypted(commonKey: newKey, password: password)
}
/// Resets all data by making all old data inaccessible
func reset() {
self.cipherData = Self.createCipherData()
self.encryptedPasswordKey = Self.makeEncryptedPasswordCommonKey(cipherData: self.cipherData)
}
/// Gets password if exists. Pass nil to get biometrics password
/// - Parameter password: current password
/// - Returns: password string (if exists)
private func getPassword(_ password: String? = nil) -> String? {
if let password {
guard let encryptedPasswordKey else { return nil }
return self.keychain[encryptedPasswordKey, password: password]
} else {
return self.passwordByBiometrics()
}
}
/// Set password
/// - Parameter password: new password
private func setPassword(_ password: String) throws {
guard let encryptedPasswordKey else {
throw WalletError.passwordKeyNotExist
}
self.keychain[encryptedPasswordKey, password: password] = password
}
/// Migrate private key in keychain from old to new for old versions
/// - Parameters:
/// - oldKey: old-style key
/// - newKey: new-style key
/// - password: current password
private func migratePrivateKey(oldKey: CommonKey, newKey: CommonKey, password: String) {
guard let privateKey = self.keychain[oldKey, password: password] else { return }
self.keychain[oldKey, password: password] = nil
self.keychain[newKey, password: password] = privateKey
}
/// Migrate to encrypted keys for keychain
/// - Parameters:
/// - commonKey: old-style common key, without encryption
/// - password: current password
private func migrateToEncrypted(commonKey: CommonKey, password: String) {
guard let privateKey = self.keychain[commonKey, password: password] else { return }
self.keychain[commonKey, password: password] = nil
self.keychain[self.encryptPK(commonKey: commonKey.rawValue), password: password] = privateKey
}
/// Migrating from an old keychain password key to a new one
/// - Parameter password: current password
private func migratePassword(password: String) {
guard self.keychain[Constants.oldPasswordKey, password: password] != nil,
let encryptedPasswordKey else { return }
self.keychain[Constants.oldPasswordKey, password: password] = nil
self.keychain[encryptedPasswordKey, password: password] = password
}
/// Makes encrypted password key for keychain
private static func makeEncryptedPasswordCommonKey(cipherData: CipherData) -> CommonKey? {
guard let encryptedCKeyString = try? Self.aesEncrypt(string: Constants.passwordKey,
key: cipherData.key,
iv: cipherData.iv)
else { return nil }
return CommonKey(encryptedCKeyString)
}
/// Encrypt key for use in keychain
/// - Parameter commonKey: common key, e.g. "user@wallet.privatekey"
/// - Returns: encrypted CommonKey object
private func encryptPK(commonKey: String) -> CommonKey {
let encryptedCKeyString = try? Self.aesEncrypt(string: commonKey,
key: self.cipherData.key,
iv: self.cipherData.iv)
return CommonKey(encryptedCKeyString ?? commonKey)
}
/// Generates data for encoding keys
private static func makeCipherData() -> CipherData {
let ckey = UUID().uuidString.replacingOccurrences(of:"-",
with: "",
options: .literal)
let iv = String(UUID().uuidString.replacingOccurrences(of:"-",
with: "",
options: .literal)
.dropLast(16))
return CipherData(key: ckey, iv: iv)
}
/// Tries to load cipher data and creates it, if doesn't exist
private static func initCipherData() -> CipherData {
if let ckey = UserDefaults.standard.string(forKey: Constants.cipherKey),
let iv = UserDefaults.standard.string(forKey: Constants.ivKey) {
return CipherData(key: ckey, iv: iv)
} else {
return Self.createCipherData()
}
}
/// Creates new data for encoding keychain keys and saves it to defaults
private static func createCipherData() -> CipherData {
let cipherData = Self.makeCipherData()
UserDefaults.standard.set(cipherData.key, forKey: Constants.cipherKey)
UserDefaults.standard.set(cipherData.iv, forKey: Constants.ivKey)
return cipherData
}
static func aesEncrypt(string: String, key: String, iv: String) throws -> String {
guard let data = string.data(using: .utf8) else {
throw WalletError.cannotEncryptNonUTF8Data
}
let encrypted = try AES(key: Array(key.utf8),
blockMode: CBC(iv: Array(iv.utf8)))
.encrypt([UInt8](data))
let encryptedData = Data(encrypted)
return encryptedData.base64EncodedString()
}
static func aesDecrypt(string: String, key: String, iv: String) throws -> String {
guard let data = Data(base64Encoded: string) else {
throw WalletError.cannotDecryptNonUTF8Data
}
let decrypted = try AES(key: Array(key.utf8),
blockMode: CBC(iv: Array(iv.utf8)))
.decrypt([UInt8](data))
let decryptedData = Data(decrypted)
guard let decryptedString = String(bytes: decryptedData.bytes, encoding: .utf8) else {
throw WalletError.cannotDecryptData
}
return decryptedString
}
}
@@ -11,7 +11,6 @@ import WalletFoundation
import WalletNetwork
final class VillageHouse: Bank {
private enum Constants {
static let oldKey = "Account.Service.collection"
static let key = "Wallet.bank.service"
@@ -24,10 +23,9 @@ final class VillageHouse: Bank {
// MARK: - Properties
private var password: String
private let device: Device
private let environment: NetworkEnvironment
private let keychainKeeper: PrivacyKeeper
private let activeSubject: CurrentValueSubject<Villager?, Never>
private let villagersSubject: CurrentValueSubject<[Villager], Never>
@@ -35,16 +33,17 @@ final class VillageHouse: Bank {
// MARK: - Init
private init(password: String, device: Device, environment: NetworkEnvironment) {
private init(password: String, keychain: PrivacyKeeper, device: Device, environment: NetworkEnvironment) {
self.password = password
self.device = device
self.environment = environment
self.keychainKeeper = keychain
let collection = Self.restore()
let collection = Self.restore(keychain: keychain)
Self.migrateIfNeeded(collection: collection, password: password)
self.villagersSubject = CurrentValueSubject(collection)
let active = Self.active(in: collection)
let active = Self.active(in: collection, keychain: keychain)
self.activeSubject = CurrentValueSubject(active)
self.villagersSubject
@@ -52,8 +51,6 @@ final class VillageHouse: Bank {
self?.save(villagers: villagers)
}
.store(in: &self.cancellables)
self.migrate(using: self.password)
}
// MARK: - Bank
@@ -65,8 +62,6 @@ final class VillageHouse: Bank {
lazy var activePublisher: AnyPublisher<Wallet?, Never> = self.activeSubject.map { $0 }.eraseToAnyPublisher()
lazy var walletsPublisher: AnyPublisher<[Wallet], Never> = self.villagersSubject.map { $0 }.eraseToAnyPublisher()
func accept(password: String) -> Bool { self.password == password }
func remove(wallet: Wallet) throws {
@@ -107,31 +102,29 @@ final class VillageHouse: Bank {
self.activeSubject.value = villager
}
func add(using walletCase: WalletCase) async throws -> Wallet {
let villager = try await Villager.create(walletCase: walletCase, on: self.device, using: self.environment)
villager.updatePrivateKey(using: self.password)
let villagers = self.villagersSubject.value
self.villagersSubject.value = villagers + [villager]
func add(using walletCase: WalletCase, password: String) async throws -> Wallet {
let villager = try await Villager.create(walletCase: walletCase,
on: self.device,
using: self.environment,
keychain: self.keychainKeeper)
self.add(wallets: [villager], password: password)
return villager
}
func add(using purses: [Purse]) throws {
func add(using purses: [Purse], password: String) throws {
guard self.keychainKeeper.accept(password: password) else {
throw WalletError.passwordNotMatch
}
let wallets = purses
.filter { $0.bank?.isEquals(other: self) ?? false }
.filter { $0.bank?.isEquals(other: self, password: password) ?? false }
.filter { purse in
!self.wallets.contains(where: { $0.name == purse.name && $0.keyType.rawValue == purse.permission.permName })
}
.map { Villager.create(purse: $0) }
.map { Villager.create(purse: $0,
keeper: self.keychainKeeper) }
wallets
.filter { $0.key.privateKey.isExist }
.forEach { $0.updatePrivateKey(using: self.password) }
let villagers = self.villagersSubject.value
self.villagersSubject.value = villagers + wallets
self.add(wallets: wallets, password: password)
}
func restore(using keys: WalletKey) async throws -> PurseHolder {
@@ -176,22 +169,21 @@ final class VillageHouse: Bank {
}
}
func isEquals(other: Bank) -> Bool {
guard let house = other as? VillageHouse else { return false }
return self.password == house.password &&
self.device.isEquals(other: house.device) &&
self.environment.isEquals(other: house.environment)
func accept(password: String) -> Bool {
self.keychainKeeper.accept(password: password)
}
func switchPassword(_ password: String, old: String) throws {
guard self.accept(password: old) else {
throw BankError.passwordNotMatch
}
self.password = password
old >>- { old in self.villagersSubject.value.forEach { $0.update(password: password, old: old) } }
func isEquals(other: Bank, password: String) -> Bool {
guard let house = other as? VillageHouse else { return false }
return self.device.isEquals(other: house.device) &&
self.environment.isEquals(other: house.environment) &&
self.keychainKeeper.accept(password: password) &&
other.accept(password: password)
}
func switchPassword(_ new: String, old: String) throws {
try self.keychainKeeper.update(password: new, old: old)
self.wallets.forEach { $0.updatePrivateKeyEncryption(password: new, old: old) }
}
func update(_ keyUpdates: [WalletKeyUpdate], using password: String) async throws -> [WalletKeyUpdateResult] {
@@ -216,32 +208,48 @@ final class VillageHouse: Bank {
// MARK: - Internal
func isEquals(password: String, device: Device, environment: NetworkEnvironment) -> Bool {
return self.password == password &&
self.device.isEquals(other: device) &&
self.environment.isEquals(other: environment)
func isEquals(device: Device, environment: NetworkEnvironment, password: String) -> Bool {
return self.device.isEquals(other: device) &&
self.environment.isEquals(other: environment) &&
self.accept(password: password)
}
// MARK: - Private
private func add(wallets: [Villager], password: String) {
wallets
.filter { $0.key.privateKey.isExist }
.forEach { $0.updateKeychain(key: $0.key, using: password) }
let villagers = self.villagersSubject.value
self.villagersSubject.value = villagers + wallets
}
private func save(villagers: [Villager]) {
Settings.shared[Constants.Keys.collection] = villagers.jsonData()
}
private static func restore() -> [Villager] {
private static func restore(keychain: PrivacyKeeper) -> [Villager] {
if let data: Data = Settings.shared[Constants.Keys.collection],
let villagers: [Villager] = data.jsonDecoded(type: Villager.self) {
let userInfoKey = Villager.keychainUserInfoKey,
let villagers: [Villager] = data.jsonDecoded(type: Villager.self,
userInfo: [userInfoKey: keychain]) {
return villagers
}
return []
}
private func migrate(using password: String) {
private static func migrateIfNeeded(collection: [Villager], password: String) {
guard collection.count > 0 else { return }
collection.forEach { $0.migrate(password: password) }
}
private func migrate() {
let migrateWallets: [Villager]
if let data = UserDefaults.standard.value(forKey: Constants.oldKey) as? Data,
let collection = try? JSONDecoder().decode([Villager.OldVillager].self, from: data) {
migrateWallets = collection.compactMap { $0.covert(password: password) }
migrateWallets = collection.compactMap { $0.covert(keeper: self.keychainKeeper) }
} else {
migrateWallets = []
}
@@ -263,14 +271,20 @@ final class VillageHouse: Bank {
// MARK: - Static
static func create(for password: String, on device: Device, environment: NetworkEnvironment) -> VillageHouse {
VillageHouse(password: password, device: device, environment: environment)
public static func create(password: String, keeper: PrivacyKeeper, on device: Device, environment: NetworkEnvironment) -> VillageHouse {
VillageHouse(password: password, keychain: keeper, device: device, environment: environment)
}
private static func active(in collection: [Villager]) -> Villager? {
public static func removeAllVillagers() {
Settings.shared[Constants.Keys.collection] = Data()
}
private static func active(in collection: [Villager], keychain: PrivacyKeeper) -> Villager? {
if let data: Data = Settings.shared[Constants.Keys.current],
let villager: Villager = data.jsonDecoded(type: Villager.self),
let userInfoKey = Villager.keychainUserInfoKey,
let villager: Villager = data.jsonDecoded(type: Villager.self,
userInfo: [userInfoKey: keychain]),
collection.contains(where: { $0 == villager }) {
return villager
}
@@ -283,8 +297,4 @@ final class VillageHouse: Bank {
return collection.first(where: { $0.name == name && $0.keyType == keyType })
}
static func removeAllVillagers() {
Settings.shared[Constants.Keys.collection] = Data()
}
}
@@ -21,13 +21,17 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
var publicKey: String
var keyType: String
func covert(password: String) -> Villager? {
func covert(keeper: PrivacyKeeper) -> Villager? {
guard let keyType = WalletKeyType(rawValue: self.keyType),
let key = try? WalletKey.restore(using: self.username, type: keyType, publicKey: self.publicKey, password: password) else {
let key = try? WalletKey.restore(using: self.username, type: keyType, publicKey: self.publicKey) else {
return nil
}
return Villager(name: self.username, key: key, keyType: keyType, state: .accepted)
return Villager(name: self.username,
key: key,
keyType: keyType,
state: .accepted,
keychain: keeper)
}
}
@@ -35,17 +39,26 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
// MARK: - Init
private init(walletCase: WalletCase, keyType: WalletKeyType, state: WalletState) {
private init(walletCase: WalletCase,
keyType: WalletKeyType,
state: WalletState,
keychain: PrivacyKeeper) {
self.name = walletCase.name
self.key = walletCase.key
self.keyType = keyType
self.keychain = keychain
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
}
private init(name: String, key: WalletKey, keyType: WalletKeyType, state: WalletState) {
private init(name: String,
key: WalletKey,
keyType: WalletKeyType,
state: WalletState,
keychain: PrivacyKeeper) {
self.name = name
self.key = key
self.keyType = keyType
self.keychain = keychain
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
}
@@ -62,6 +75,10 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
case state
}
static var keychainUserInfoKey: CodingUserInfoKey? {
return CodingUserInfoKey(rawValue: "keychain")
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -77,6 +94,11 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
let state = try container.decode(WalletState.self, forKey: .state)
self.stateSubject = CurrentValueSubject<WalletState, Never>(state)
guard let keychainKey = Self.keychainUserInfoKey,
let keeper = decoder.userInfo[keychainKey] as? PrivacyKeeper else {
throw WalletError.decodingWithoutKeychainContext
}
self.keychain = keeper
// If first version we hold key on flat structure!
if let key = try? WalletKey(from: decoder) {
@@ -84,6 +106,7 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
} else {
self.key = try container.decode(WalletKey.self, forKey: .key)
}
// TODO: - NEED COMPLETETASK
// self.key = try WalletKey.restore(using: self.name, type: self.keyType, publicKey: publicKey, password: "")
}
@@ -102,6 +125,7 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
private(set) var key: WalletKey
let keyType: WalletKeyType
var state: WalletState { self.stateSubject.value }
private let keychain: PrivacyKeeper
lazy var statePublisher: AnyPublisher<WalletState, Never> = self.stateSubject.eraseToAnyPublisher()
@@ -143,52 +167,65 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
self.stateSubject.value = state
}
func update(key: WalletKey, using passrod: String) -> Wallet {
self.key = key
self.updatePrivateKey(using: passrod)
@discardableResult
func updateKeychain(key: WalletKey, using password: String) -> Wallet {
if self.keychain.accept(password: password) {
self.key = key
self.updatePrivateKey(password: password)
}
return self
}
func privateKey(_ value: String?) -> String? {
let privateKey: String?
if let password = value {
privateKey = WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey), password: password]
} else {
privateKey = WalletKeychain.instance[biometric: .key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)]
}
return privateKey
func privateKey(password: String?) -> String? {
guard let password, self.keychain.accept(password: password) else { return nil }
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
return self.keychain.privateKey(for: key.rawValue, password: password)
}
func updatePrivateKey(using password: String) {
private func updatePrivateKey(password: String) {
guard let privateKey = self.key.privateKey else { return }
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey),
password: password] = privateKey
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
self.keychain.update(privateKey: privateKey, for: key.rawValue, password: password)
}
// TODO: - Need review
func migrate(password: String) {
let privateKey = WalletKeychain.instance[.key(self.name, suffix: .privateKey), password: password]
WalletKeychain.instance[.key(self.name, suffix: .privateKey), password: ""] = nil
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey), password: password] = privateKey
let oldKey = CommonKey.key(self.name, suffix: .privateKey)
let newKey = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
self.keychain.migrate(oldKey: oldKey, newKey: newKey, password: password)
}
func update(password: String, old: String) {
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey),
password: password] = self.privateKey(old)
/// Updates storage of private key in keychain for new password
/// - Parameters:
/// - password: new password
/// - old: old password (which was stored with private key earlier)
func updatePrivateKeyEncryption(password: String, old: String) {
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
let privateKey = self.privateKey(password: old)
self.keychain.update(privateKey: nil, for: key.rawValue, password: old)
self.keychain.update(privateKey: privateKey, for: key.rawValue, password: password)
}
func clear() {
WalletKeychain.instance[.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey), password: ""] = nil
let key = CommonKey.key("\(self.name)@\(self.keyType.rawValue)", suffix: .privateKey)
self.keychain.update(privateKey: nil, for: key.rawValue, password: "")
}
// MARK: - Static
static func create(purse: Purse) -> Villager {
Villager(name: purse.name, key: purse.key, keyType: purse.keyType, state: .accepted)
static func create(purse: Purse, keeper: PrivacyKeeper) -> Villager {
Villager(name: purse.name,
key: purse.key,
keyType: purse.keyType,
state: .accepted,
keychain: keeper)
}
static func create(walletCase: WalletCase, on device: Device, using environment: NetworkEnvironment) async throws -> Villager {
static func create(walletCase: WalletCase,
on device: Device,
using environment: NetworkEnvironment,
keychain: PrivacyKeeper) async throws -> Villager {
let service = AccountService(environment: environment)
do {
@@ -205,12 +242,18 @@ final class Villager: Wallet, Codable, CustomStringConvertible {
case .completed: state = .accepted
}
return Villager(walletCase: walletCase, keyType: .owner, state: state)
return Villager(walletCase: walletCase,
keyType: .owner,
state: state,
keychain: keychain)
} catch let NetworkServiceError.gqlApplication(error) {
if ApplicationSettings.ignoreCreateAccountSatus {
return Villager(walletCase: walletCase, keyType: .owner, state: .creating("HELLO"))
return Villager(walletCase: walletCase,
keyType: .owner,
state: .creating("HELLO"),
keychain: keychain)
}
let walletError: WalletError
@@ -12,7 +12,6 @@ import WalletNetwork
public enum BankError: Error {
case empty
case notOwned
case passwordNotMatch
}
/// Bank that contain wallets.
@@ -25,14 +24,12 @@ public protocol Bank: AnyObject {
var wallets: [Wallet] { get }
/// Publisher for observe wallet changes.
var walletsPublisher: AnyPublisher<[Wallet], Never> { get }
/// Check password
func accept(password: String) -> Bool
/// Activate wallet
func activate(wallet: Wallet?) throws
/// Create new wallet in bank.
func add(using walletCase: WalletCase) async throws -> Wallet
func add(using walletCase: WalletCase, password: String) async throws -> Wallet
/// Add Purse to bank with converting to Wallet
func add(using purses: [Purse]) throws
func add(using purses: [Purse], password: String) throws
/// Restore wallet by key.
func restore(using keys: WalletKey) async throws -> PurseHolder
/// Remove wallet from bank.
@@ -42,7 +39,9 @@ public protocol Bank: AnyObject {
/// Update WalletKey for Wallet
func update(_ keyUpdates: [WalletKeyUpdate], using password: String) async throws -> [WalletKeyUpdateResult]
/// Compare two banks
func isEquals(other: Bank) -> Bool
func isEquals(other: Bank, password: String) -> Bool
/// Switch password
func switchPassword(_ password: String, old: String) throws
/// Check if password valid for this bank
func accept(password: String) -> Bool
}
@@ -33,6 +33,18 @@ public enum WalletError: Error {
case privateKeyNotExists
/// Wallet with such name already exists.
case exists
/// Input password doesn't match keychain password
case passwordNotMatch
/// Error creating access key for password in keychain
case passwordKeyNotExist
/// Failed encryption access key for keychain
case cannotEncryptNonUTF8Data
/// Failed decryption access key for keychain
case cannotDecryptNonUTF8Data
/// Common decrypt error
case cannotDecryptData
/// Trying to decode Wallet-protocol object without keychain data
case decodingWithoutKeychainContext
}
/// Wallet state
@@ -127,12 +139,13 @@ public protocol Wallet: AnyObject {
var keyType: WalletKeyType { get }
/// State
var state: WalletState { get }
var statePublisher: AnyPublisher<WalletState, Never> { get }
@discardableResult
func update(key: WalletKey, using passrod: String) -> Wallet
func privateKey(_ value: String?) -> String?
func updateKeychain(key: WalletKey, using password: String) -> Wallet
func privateKey(password: String?) -> String?
func updatePrivateKeyEncryption(password: String, old: String)
/// Compare two Wallets
func isEquals(other: Wallet) -> Bool
@@ -135,7 +135,7 @@ public enum WalletKey: Codable, Equatable {
// MARK: - Internal static
static func restore(using name: String, type: WalletKeyType, publicKey: String, password: String) throws -> WalletKey {
static func restore(using name: String, type: WalletKeyType, publicKey: String) throws -> WalletKey {
throw WalletKeyError.unlock
}
@@ -0,0 +1,99 @@
//
// JSONParsingTests.swift
//
//
// Created by Nut.Tech on 17.02.2023.
//
import XCTest
@testable import WalletKit
fileprivate struct User: Decodable {
let id: Int
let name: String
var employer: String?
static var employerUserInfoKey: CodingUserInfoKey? {
return CodingUserInfoKey(rawValue: "employer")
}
private enum CodingKeys: String, CodingKey {
case id
case name
}
init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
self.id = try keyedContainer.decode(Int.self, forKey: .id)
self.name = try keyedContainer.decode(String.self, forKey: .name)
guard let employerKey = Self.employerUserInfoKey,
let employer = decoder.userInfo[employerKey] as? String else {
return
}
self.employer = employer
}
}
final class JSONParsingTests: XCTestCase {
private enum Locals {
static let username1 = "Username1"
static let username2 = "Username2"
static let userId1 = 1
static let userId2 = 2
static let companyName = "Company Name"
}
func testJsonDecoded() {
let jsonData = """
{"id": \(Locals.userId1), "name": "\(Locals.username1)"}
""".data(using: .utf8)!
guard let user: User = jsonData.jsonDecoded(type: User.self) else {
XCTFail("Error decoding data")
return
}
XCTAssertEqual(user.id, Locals.userId1)
XCTAssertEqual(user.name, Locals.username1)
}
func testJsonDecodedArray() {
let jsonData = """
[{"id": \(Locals.userId1), "name": "\(Locals.username1)"}, {"id": \(Locals.userId2), "name": "\(Locals.username2)"}]
""".data(using: .utf8)!
guard let users: [User] = jsonData.jsonDecoded(type: User.self) else {
XCTFail("Error decoding data")
return
}
XCTAssertEqual(users.count, 2)
XCTAssertEqual(users[0].id, Locals.userId1)
XCTAssertEqual(users[0].name, Locals.username1)
XCTAssertEqual(users[1].id, Locals.userId2)
XCTAssertEqual(users[1].name, Locals.username2)
}
func testJsonDecodedWithUserInfo() {
let jsonData = """
{"id": \(Locals.userId1), "name": "\(Locals.username1)"}
""".data(using: .utf8)!
guard let key = CodingUserInfoKey(rawValue: "employer") else {
XCTFail("Error creating user key")
return
}
let userInfo = [key: Locals.companyName]
guard let user: User = jsonData.jsonDecoded(type: User.self, userInfo: userInfo) else {
XCTFail("Error decoding data")
return
}
XCTAssertEqual(user.id, Locals.userId1)
XCTAssertEqual(user.name, Locals.username1)
XCTAssertEqual(user.employer, Locals.companyName)
}
}
@@ -0,0 +1,75 @@
//
// KeychainMock.swift
//
//
// Created by NUT.TECH on 16.02.2023.
//
import Foundation
import WalletFoundation
final class KeychainMock: KeychainProtocol {
private enum KeychainMockLocals {
static let password = "PWD"
static let biometric = "BIO"
}
typealias Value = (value: String, password: String)
private var storage: [String: Value]
init(storage: [String: Value]? = nil) {
self.storage = storage ?? [:]
}
func exist(_ key: Key) -> Bool {
let commonKey = key.with(KeychainMockLocals.password)
return self.storage[commonKey.rawValue].isExist
}
func bioExist(_ key: Key) -> Bool {
let commonKey = key.with(KeychainMockLocals.biometric)
return self.storage[commonKey.rawValue] != nil
}
subscript(biometric key: Key) -> String? {
let commonKey = key.with(KeychainMockLocals.biometric).rawValue
let object = self.storage[commonKey]
return object?.value
}
subscript(key: Key, password password: String) -> String? {
get {
let commonKey = key.with(KeychainMockLocals.password).rawValue
let object = self.storage[commonKey]
return object?.password == password ? object?.value : nil
}
set {
self.update(key, password: password, newValue: newValue)
}
}
private func update(_ key: Key, password: String, newValue: String?) {
if let value = newValue {
self.updateStorage(key: key.with(KeychainMockLocals.password), password: password, value: value)
self.updateStorage(key: key.with(KeychainMockLocals.biometric), password: password, value: value)
} else {
self.updateStorage(key: key.with(KeychainMockLocals.password), password: password, value: nil)
self.updateStorage(key: key.with(KeychainMockLocals.biometric), password: password, value: nil)
}
}
private func updateStorage(key: Key, password: String, value: String?) {
let stringKey = key.rawValue
if self.storage[stringKey] != nil, self.storage[stringKey]?.password == password {
if let value {
self.storage[stringKey]?.value = value
} else {
self.storage.removeValue(forKey: stringKey)
}
} else if let value {
self.storage[stringKey] = Value(value: value, password: password)
}
}
}
@@ -0,0 +1,103 @@
//
// PrivacyKeeperTests.swift
//
//
// Created by user on 16.02.2023.
//
import XCTest
@testable import WalletKit
@testable import WalletFoundation
final class PrivacyKeeperTests: XCTestCase {
func testAES() throws {
let commonKey = "PrivacyKeeper.password"
let encryptedKey = "mdHBVh0lxLT8VBJnL9MYuM/pxWi/QmTCXmW8YZQO8Cs="
let key = "96080AE5042241D9BFF30330C9625201"
let iv = "7C515B5233314B42"
do {
let encryptedCommonKey = try PrivacyKeeper.aesEncrypt(string: commonKey, key: key, iv: iv)
let decryptedCommonKey = try PrivacyKeeper.aesDecrypt(string: encryptedCommonKey, key: key, iv: iv)
XCTAssertEqual(encryptedCommonKey, encryptedKey, "Incorrect encryption result")
XCTAssertEqual(decryptedCommonKey, commonKey, "Incorrect decryption result")
} catch {
XCTFail(error.localizedDescription)
}
}
func testPassword() {
let password = "0000"
let keychainMock = KeychainMock()
let keeper = PrivacyKeeper(keychain: keychainMock)
do {
XCTAssertFalse(keeper.isPasswordExists, "Incorrect isPasswordExists result")
try keeper.update(password: password)
XCTAssertTrue(keeper.isPasswordExists, "Incorrect isPasswordExists result")
XCTAssertTrue(keeper.accept(password: password), "Incorrect password")
let newPassword = "1111"
try keeper.update(password: newPassword, old: password)
XCTAssertTrue(keeper.accept(password: newPassword), "Incorrect password")
let incorrectPassword = "2222"
XCTAssertFalse(keeper.accept(password: incorrectPassword), "Incorrect password check")
keeper.reset()
XCTAssertFalse(keeper.accept(password: newPassword), "Incorrect password reset")
XCTAssertFalse(keeper.isPasswordExists, "Incorrect isPasswordExists result")
} catch {
XCTFail(error.localizedDescription)
}
}
func testPrivateKey() {
let password = "0000"
let privateKey = "privateKey1111"
let commonKey = "accountKey@malinka.privateKey"
let keychainMock = KeychainMock()
let keeper = PrivacyKeeper(keychain: keychainMock)
do {
try keeper.update(password: password)
XCTAssertTrue(keeper.accept(password: password), "Incorrect password")
keeper.update(privateKey: privateKey,
for: commonKey,
password: password)
let readPrivateKey = keeper.privateKey(for: commonKey, password: password)
XCTAssertNotNil(readPrivateKey, "Error setting private key to keychain")
XCTAssertEqual(readPrivateKey, privateKey, "Incorrect private key stored in keychain")
keeper.reset()
let nilPrivateKey = keeper.privateKey(for: commonKey, password: password)
XCTAssertNil(nilPrivateKey, "Incorrect private key reset")
} catch {
XCTFail(error.localizedDescription)
}
}
func testMigration() {
let password = "0000"
let privateKey = "privateKey1111"
let privateKey2 = "privateKey2222"
let migratedCommonKey = "accountName@owner.privateKey"
let keyToMigrate = CommonKey("accountKey2@malinka.privateKey")
let oldKey = CommonKey.key("accountName", suffix: "privateKey")
let newKey = CommonKey.key("accountName@owner", suffix: "privateKey")
let storage = ["accountName.privateKey.PWD": KeychainMock.Value(value: privateKey, password: password),
"PrivacyKeeper.password.PWD": KeychainMock.Value(value: password, password: password),
"accountKey2@malinka.privateKey.PWD": KeychainMock.Value(value: privateKey2, password: password)]
let keychainMock = KeychainMock(storage: storage)
let keeper = PrivacyKeeper(keychain: keychainMock)
keeper.migrate(oldKey: oldKey, newKey: newKey, password: password)
XCTAssertNil(keeper.privateKey(for: oldKey.rawValue, password: password), "Incorrect private key")
XCTAssertEqual(keeper.privateKey(for: migratedCommonKey, password: password), privateKey, "Incorrect private key")
keeper.migrate(oldKey: keyToMigrate, newKey: keyToMigrate, password: password)
XCTAssertEqual(keeper.privateKey(for: keyToMigrate.rawValue, password: password), privateKey2, "Incorrect private key")
}
}
@@ -205,7 +205,9 @@ final class AccountControllerManual: UIViewController {
for (_, accounts) in accountsToAdd {
do {
try Accounts().addAccounts(purses: accounts.map(\.purse))
if let password = Account.Service.Authorize.shared.password {
try Accounts().addAccounts(purses: accounts.map(\.purse), password: password)
}
Loader.hide(in: self)
} catch {
Loader.hide(in: self)
@@ -73,28 +73,26 @@ final class SettingsViewController: UIViewController {
let ctrl = UINavigationController(rootViewController: accountControollerPin)
accountControollerPin.type = .old
accountControollerPin.viewModel.validatePin = { [weak ctrl, weak self] (pin: String) -> Bool in
guard let password = Account.Service.getPassword(password: pin),
!password.isEmpty, password == pin, password.count == 4 else {
guard Account.Service.check(password: pin), !pin.isEmpty, pin.count == 4 else {
Account.Service.Authorize.shared.savePinTry()
return false
}
ctrl?.dismiss(animated: false)
Account.Service.Authorize.shared.update(password: password)
Account.Service.Authorize.shared.update(password: pin)
let accountControollerPin = WalletPinController.instantiate()
let ctrl = UINavigationController(rootViewController: accountControollerPin)
accountControollerPin.type = .new
accountControollerPin.viewModel.validatePin = { [weak ctrl] (pin: String) -> Bool in
// TODO: It seems that there needs to place if Accounts().bank.accept(password:) instead of Account.Service.getPassword(password:), but compromise solution is to refactor it after 1.4.0
if let oldPassword = Account.Service.getPassword(password: password) {
accountControollerPin.viewModel.validatePin = { [weak ctrl] (newPin: String) -> Bool in
if Account.Service.check(password: pin) {
do {
try Accounts().update(password: pin, old: oldPassword)
try Accounts().update(password: newPin, old: pin)
} catch {
Alert.error(text: L10n.Account.Password.currentWrong)
return false
}
Account.Service.Authorize.shared.update(password: pin)
Account.Service.Authorize.shared.update(password: newPin)
ctrl?.dismiss(animated: true)
return true
} else {
@@ -143,7 +141,7 @@ final class SettingsViewController: UIViewController {
let ctrl = UINavigationController(rootViewController: accountControollerPin)
accountControollerPin.type = .new
accountControollerPin.viewModel.validatePin = { [weak ctrl] (pin: String) -> Bool in
if let oldPassword = Account.Service.getPassword(password: oldPassword) {
if Account.Service.check(password: oldPassword) {
do {
try Accounts().update(password: pin, old: oldPassword)
UserDefaults.standard.setValue(!UserDefaults.standard.bool(forKey: "isPinPwdMode"), forKey: "isPinPwdMode")
@@ -121,9 +121,9 @@ extension Account {
// MARK: - Edit
extension Account.Service {
func addAccounts(purses: [Purse]) throws {
func addAccounts(purses: [Purse], password: String) throws {
try self.bank.add(using: purses)
try self.bank.add(using: purses, password: password)
if let wallet = self.collection.last,
let book = self.messageShelf.book(for: wallet) {
@@ -150,7 +150,7 @@ extension Account.Service {
$0.name == accountToChange.wallet.name && $0.keyType == accountToChange.wallet.keyType
}) else { continue }
account.update(key: accountToChange.key, using: password)
account.updateKeychain(key: accountToChange.key, using: password)
}
}
@@ -158,7 +158,7 @@ extension Account.Service {
for accountToChange in accounts {
guard let oldPrivateKey = Accounts().collection.first(where: { $0.name == accountToChange.wallet.name
&& $0.keyType == accountToChange.keyType })?.privateKey(password) else { continue }
&& $0.keyType == accountToChange.keyType })?.privateKey(password: password) else { continue }
guard let msgsService = try? CryptoChat.Service.MsgsHistory(username: accountToChange.wallet.name, encryptionKey: oldPrivateKey) else { continue }
@@ -215,10 +215,9 @@ extension Account.Service {
var isAuthorized: Bool {
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
if let pin = Account.Service.Authorize.shared.password,
let password = Self.getPassword(password: pin),
!password.isEmpty,
password == pin,
password.count == 4 {
Self.check(password: pin),
!pin.isEmpty,
pin.count == 4 {
return true
} else {
return false
@@ -228,14 +227,13 @@ extension Account.Service {
}
}
static func getPassword(password: String?) -> String? { Village.getPassword(password: password) }
static var isPasswordExists: Bool { Village.isPasswordExists }
static var isBiometricksEnabled: Bool {
get { Settings.shared[isBiometricksEnabledKey] }
set { Settings.shared[isBiometricksEnabledKey] = newValue }
}
static var isPasswordExists: Bool { Village.isPasswordExists }
static var isAccountMigrated: Bool {
get { Settings.shared[isAccountMigratedKey] }
set { Settings.shared[isAccountMigratedKey] = newValue }
@@ -255,14 +253,13 @@ extension Account.Service {
DispatchQueue.main.async { completion(success) }
}
}
static func check(password: String) -> Bool { Village.accept(password: password) }
static func passwordViaBiometrics() -> String? { Village.passwordViaBiometrics() }
func update(password: String, old: String? = nil) throws {
try old >>- {
//Compromise solution for 1.4.0
//TODO: Refactor this later
try Village.updateCommonPassword(password: password, old: $0)
try self.bank.switchPassword(password, old: $0)
}
try old >>- { try self.bank.switchPassword(password, old: $0) }
}
func validate(password value: String) -> Bool { value.count >= 4 }
@@ -9,6 +9,7 @@
import Foundation
import Combine
import WalletFoundation
import WalletKit
private let pinTriesKey = CommonKey("Account.Service.Authorize.pinTries")
@@ -43,7 +44,12 @@ extension Account.Service {
func update(password value: String?, type: UpdateType = .change) {
switch type {
case .change: self.password = value
case .change:
if self.password == nil, let value {
// TODO: Call only once on initialization, not on update! Needs to refactor in AccountServiceAuthorize task
try? Village.set(password: value)
}
self.password = value
case .toggle: self.updateAuthSubject.send(())
case .both:
self.password = value
@@ -48,7 +48,7 @@ extension Account.Service {
_ completion: @escaping Completion) {
guard let wallet = accounts.first?.wallet,
let privateKey = wallet.privateKey(password) else {
let privateKey = wallet.privateKey(password: password) else {
completion(.success("None"))
return
}
@@ -54,19 +54,13 @@ final class AccountViewAuthorize: CommonViewCustom {
static func getPrivateKey() -> String {
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
if let pin = Account.Service.Authorize.shared.password,
let password = Account.Service.getPassword(password: pin),
!password.isEmpty,
password == pin,
password.count == 4,
let value = Accounts().current?.privateKey(password) {
return value
}
return ""
} else {
return ""
}
if UserDefaults.standard.bool(forKey: "isPinPwdMode"),
let pin = Account.Service.Authorize.shared.password,
Account.Service.check(password: pin),
let value = Accounts().current?.privateKey(password: pin) {
return value
}
return ""
}
static func showGetPassword(in viewController: UIViewController,
@@ -92,14 +86,11 @@ final class AccountViewAuthorize: CommonViewCustom {
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
if let pin = Account.Service.Authorize.shared.password,
let password = Account.Service.getPassword(password: pin),
!password.isEmpty,
password == pin,
password.count == 4 {
Account.Service.check(password: pin),
!pin.isEmpty,
pin.count == 4 {
completion(isPasswordRequired
? password
: Accounts().current?.privateKey(password) ?? "")
completion(isPasswordRequired ? pin : Accounts().current?.privateKey(password: pin) ?? "")
return
}
@@ -107,18 +98,15 @@ final class AccountViewAuthorize: CommonViewCustom {
let ctrl = UINavigationController(rootViewController: accountControollerPin)
accountControollerPin.showBackButton = showPinBackButton
accountControollerPin.viewModel.validatePin = { [weak ctrl] (pin: String) -> Bool in
if let password = Account.Service.getPassword(password: pin),
!password.isEmpty,
password == pin,
password.count == 4 {
if Account.Service.check(password: pin),
!pin.isEmpty,
pin.count == 4 {
ctrl?.dismiss(animated: true)
Account.Service.Authorize.shared.update(password: password, type: .both)
Account.Service.Authorize.shared.update(password: pin, type: .both)
completion(isPasswordRequired
? password
: Accounts().current?.privateKey(password) ?? "")
completion(isPasswordRequired ? pin : Accounts().current?.privateKey(password: pin) ?? "")
Account.Service.Authorize.shared.clearPinTries()
return true
@@ -128,16 +116,16 @@ final class AccountViewAuthorize: CommonViewCustom {
}
}
accountControollerPin.viewModel.submitBiometric = { [weak ctrl] in
if let password = Account.Service.getPassword(password: nil),
!password.isEmpty,
password.count == 4 {
if let pin = Account.Service.passwordViaBiometrics(),
!pin.isEmpty,
pin.count == 4 {
Account.Service.Authorize.shared.update(password: password, type: .both)
Account.Service.Authorize.shared.update(password: pin, type: .both)
ctrl?.dismiss(animated: true) {
completion(isPasswordRequired
? password
: Accounts().current?.privateKey(password) ?? "")
? pin
: Accounts().current?.privateKey(password: pin) ?? "")
}
}
}
@@ -167,13 +155,15 @@ final class AccountViewAuthorize: CommonViewCustom {
@IBAction private func onSubmit(_ : AnyObject?) {
if isGetPassword {
if let password = Account.Service.getPassword(password: textField.lzText ?? ""), !password.isEmpty {
self.didSubmit?(password)
if let pin = textField.lzText, Account.Service.check(password: pin), !pin.isEmpty {
self.didSubmit?(pin)
} else {
self.textField.error = L10n.Account.Authorize.error
}
} else {
if let privateKey = Accounts().current?.privateKey(textField.lzText ?? ""), !privateKey.isEmpty {
if let pin = textField.lzText,
let privateKey = Accounts().current?.privateKey(password: pin),
!privateKey.isEmpty {
self.didSubmit?(privateKey)
} else {
self.textField.error = L10n.Account.Authorize.error
@@ -183,14 +173,14 @@ final class AccountViewAuthorize: CommonViewCustom {
@IBAction private func onFaceId(_ : AnyObject?) {
if isGetPassword {
if let password = Account.Service.getPassword(password: nil),
if let password = Account.Service.passwordViaBiometrics(),
!password.isEmpty {
self.didSubmit?(password)
} else {
self.textField.error = L10n.Account.Authorize.error
}
} else {
if let privateKey = Accounts().current?.privateKey(nil),
if let privateKey = Accounts().current?.privateKey(password: nil),
!privateKey.isEmpty {
self.didSubmit?(privateKey)
} else {
@@ -77,14 +77,8 @@ final class AccountViewPassword: CommonViewCustom {
}
@IBAction private func onSubmit(_ : AnyObject?) {
guard let oldPassword = Account.Service.getPassword(password: passwordTxtFld.value) else {
Alert.error(text: L10n.Account.Password.currentWrong)
return
}
do {
try Accounts().update(password: newPasswordTxtFld.value, old: oldPassword)
try Accounts().update(password: newPasswordTxtFld.value, old: passwordTxtFld.value)
didSubmit?()
} catch {
Alert.error(text: L10n.Account.Password.currentWrong)
@@ -74,5 +74,4 @@ final class AccountViewSettings: CommonViewCustom {
@IBAction private func onChangeProtection(_ sender: Any) {
self.actionSubject.send(.changeProtection)
}
}
@@ -7,6 +7,7 @@
//
import UIKit
import WalletKit
final class WalletPinController: UIViewController {
@@ -216,13 +217,14 @@ final class WalletPinController: UIViewController {
if repeatPin == Constants.NoPinDigits,
pinCount == Constants.maxPinLength {
self.headerLabelRepeatSetup()
self.repeatPin = pin
self.repeatPin = self.pin
self.pin = []
} else if repeatPin == Constants.maxPinLength,
pinCount == Constants.maxPinLength,
self.repeatPin == self.pin {
Account.Service.Authorize.shared.update(password: self.pin.map { "\($0)" }.joined())
successShow()
let pinCode = self.pin.map { "\($0)" }.joined()
Account.Service.Authorize.shared.update(password: pinCode)
self.successShow()
}
}
}
@@ -28,8 +28,8 @@ struct CreateWalletService {
// MARK: - Methods
func create(for password: String) async throws -> UpdateWalletStateService {
let bank = self.village.getBank(with: password, on: self.device)
let wallet = try await bank.add(using: self.walletCase)
let bank = try self.village.getBank(with: password, on: self.device)
let wallet = try await bank.add(using: self.walletCase, password: password)
return UpdateWalletStateService(bank: bank, wallet: wallet)
}
}
@@ -152,7 +152,7 @@ final class OnBoardingWalletController: UIViewController {
guard let device = try? await village.device() else { throw NSError() }
let bank = village.getBank(with: password, on: device)
let bank = try village.getBank(with: password, on: device)
Account.Service.shared = Account.Service(using: bank)
self.loadingGroup.notify(queue: DispatchQueue.main) {