Skip to content

Commit 7df0964

Browse files
committed
Merge branch 'feature/pre-release' into develop
2 parents 021894c + 7603712 commit 7df0964

File tree

3 files changed

+42
-3
lines changed

3 files changed

+42
-3
lines changed

Kukai Mobile/Modules/Onboarding/ConfirmPasscodeViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class ConfirmPasscodeViewController: UIViewController {
5151
// Else if part of onboarding flow
5252
StorageService.setCompletedOnboarding(true)
5353

54-
if CurrentDevice.biometricTypeAuthorized() == .unavailable {
54+
if CurrentDevice.biometricTypeAuthorized() == .unavailable || !StorageService.wasBiometricsAccessibleDuringOnboarding() {
5555
self.navigateNonBiometric()
5656
} else {
5757
self.performSegue(withIdentifier: "biometric", sender: nil)

Kukai Mobile/Modules/Side Menu/SideMenuSecurityViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class SideMenuSecurityViewModel: ViewModel, UITableViewDiffableDataSourceHandler
5858
var snapshot = NSDiffableDataSourceSnapshot<Int, AnyHashable>()
5959
var options: [[AnyHashable]] = []
6060

61-
if CurrentDevice.biometricTypeAuthorized() != .unavailable {
61+
if CurrentDevice.biometricTypeAuthorized() != .unavailable && StorageService.wasBiometricsAccessibleDuringOnboarding() {
6262
let biometricType = CurrentDevice.biometricTypeSupported()
6363
let title = biometricType == .faceID ? "Face ID" : "Touch ID"
6464
let image = biometricType == .faceID ? UIImage(systemName: "faceid") : UIImage(systemName: "touchid")

Kukai Mobile/Services/StorageService.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ public class StorageService {
1919
static let passcode = "app.kukai.login.passcode"
2020
static let passcodeBiometric = "app.kukai.login.passcode.biometric"
2121
static let isBiometricEnabled = "app.kukai.login.biometric.enabled"
22+
static let isUserPresenceAvailable = "app.kukai.login.presence.available"
23+
static let wasBiometricAccesisbleDuringOnboarding = "app.kukai.login.biometrics.accessible.onboarding"
2224

2325
static let loginWrongGuessCount = "app.kukai.login.count"
2426
static let loginWrongGuessDelay = "app.kukai.login.delay"
@@ -99,16 +101,25 @@ public class StorageService {
99101
return .failure
100102
}
101103

104+
public static func wasBiometricsAccessibleDuringOnboarding() -> Bool {
105+
return KeychainSwift().getBool(StorageService.KeychainKeys.wasBiometricAccesisbleDuringOnboarding) ?? false
106+
}
107+
102108
/// Delete previous record (if present) and create a new one with usePresence set
103109
private static func recordPasscode(_ passcode: String) -> PasscodeStoreResult {
104110
guard let hash = Sodium.shared.pwHash.str(passwd: passcode.bytes, opsLimit: Sodium.shared.pwHash.OpsLimitInteractive, memLimit: Sodium.shared.pwHash.MemLimitInteractive) else {
105111
return .failure
106112
}
107113

114+
// There is a strange iOS issue with users who have damaged the hardware connected to their biometrics.
115+
// iOS will say the phone supports biometrics, but app is unable to make use of it, returning an error.
116+
// So to allow people to install the app, and not implement a security hole. We will check if its possible to use userPresence with a dummy keychain value
117+
// If this fails during onboard, all biometric logic will be turned off
118+
//
108119
// Recording the passcode only once with biometic flag, means when biometric enabled, users are unable to enter the passcode on its own (i.e. tapping cancel to biometric)
109120
// Because retrieving the passcode to do the verification requires succesful biometrics
110121
// Instead it needs to be stored twice, once where it can be accessed without biometrics and one with to enable both cases
111-
let res1 = recordPasscodeHash(hash, withUserPresence: true)
122+
let res1 = isUserPresenceAvailable() ? recordPasscodeHash(hash, withUserPresence: true) : true
112123
let res2 = recordPasscodeHash(hash, withUserPresence: false)
113124

114125
if res1 == false {
@@ -149,6 +160,34 @@ public class StorageService {
149160
return status == 0
150161
}
151162

163+
private static func isUserPresenceAvailable() -> Bool {
164+
guard let accessControl = SecAccessControlCreateWithFlags(nil, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, .biometryAny, nil),
165+
let hashData = "available".data(using: .utf8) else {
166+
return false
167+
}
168+
169+
KeychainSwift().delete(StorageService.KeychainKeys.isUserPresenceAvailable)
170+
let query = [
171+
kSecClass: kSecClassGenericPassword,
172+
kSecAttrAccount: StorageService.KeychainKeys.isUserPresenceAvailable,
173+
kSecValueData: hashData,
174+
kSecAttrAccessControl: accessControl,
175+
kSecReturnData: true
176+
] as [CFString: Any] as CFDictionary
177+
178+
var result: AnyObject?
179+
let status = SecItemAdd(query, &result)
180+
181+
// Record what happened for later retreival without triggering biometrics to check
182+
if status != 0 {
183+
KeychainSwift().set(false, forKey: StorageService.KeychainKeys.wasBiometricAccesisbleDuringOnboarding, withAccess: .accessibleWhenUnlockedThisDeviceOnly)
184+
return false
185+
} else {
186+
KeychainSwift().set(true, forKey: StorageService.KeychainKeys.wasBiometricAccesisbleDuringOnboarding, withAccess: .accessibleWhenUnlockedThisDeviceOnly)
187+
return true
188+
}
189+
}
190+
152191
private static func getPasscode(withUserPresence: Bool, completion: @escaping ((String?) -> Void)) {
153192
if withUserPresence {
154193

0 commit comments

Comments
 (0)