-
-
Notifications
You must be signed in to change notification settings - Fork 237
Expand file tree
/
Copy pathSecurityAvailabilityResolver.kt
More file actions
97 lines (84 loc) · 3.62 KB
/
Copy pathSecurityAvailabilityResolver.kt
File metadata and controls
97 lines (84 loc) · 3.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.sensitiveinfo.internal.crypto
import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators
import androidx.core.content.getSystemService
import com.margelo.nitro.sensitiveinfo.BiometryStatus
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
internal data class SecurityAvailabilitySnapshot(
val secureEnclave: Boolean,
val strongBox: Boolean,
val biometry: Boolean,
val biometryStatus: BiometryStatus,
val strongBiometrics: Boolean,
val deviceCredential: Boolean
)
/**
* Caches hardware capability checks (StrongBox, biometrics, device credential).
*
* The Android system calls here can be relatively expensive, so we memoize the result until the
* process restarts. JS can always request a fresh snapshot by calling
* `getSupportedSecurityLevels()`.
*/
internal class SecurityAvailabilityResolver(private val context: Context) {
private val lock = ReentrantLock()
private var cached: SecurityAvailabilitySnapshot? = null
fun resolve(): SecurityAvailabilitySnapshot {
lock.withLock {
val cachedSnapshot = cached
if (cachedSnapshot != null) {
return cachedSnapshot
}
val biometricManager = BiometricManager.from(context)
val strongResult = biometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG)
val weakResult = biometricManager.canAuthenticate(Authenticators.BIOMETRIC_WEAK)
val hasStrongBiometrics = strongResult == BiometricManager.BIOMETRIC_SUCCESS
val hasWeakBiometrics = weakResult == BiometricManager.BIOMETRIC_SUCCESS
val hasBiometry = hasStrongBiometrics || hasWeakBiometrics
// Combine the strong/weak probe results so that the most informative reason wins.
// Order of precedence: SUCCESS > NONE_ENROLLED > NO_HARDWARE/HW_UNAVAILABLE/SECURITY_UPDATE_REQUIRED > UNKNOWN/UNSUPPORTED.
val biometryStatus = classifyBiometryStatus(strongResult, weakResult)
val keyguard = context.getSystemService<KeyguardManager>()
val deviceCredential = keyguard?.isDeviceSecure == true
val hasStrongBox = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
context.packageManager.hasSystemFeature("android.hardware.strongbox_keystore")
val snapshot = SecurityAvailabilitySnapshot(
secureEnclave = hasStrongBox,
strongBox = hasStrongBox,
biometry = hasBiometry,
biometryStatus = biometryStatus,
strongBiometrics = hasStrongBiometrics,
deviceCredential = deviceCredential
)
cached = snapshot
return snapshot
}
}
private fun classifyBiometryStatus(strongResult: Int, weakResult: Int): BiometryStatus {
// SUCCESS on either tier means we can authenticate now.
if (strongResult == BiometricManager.BIOMETRIC_SUCCESS ||
weakResult == BiometricManager.BIOMETRIC_SUCCESS
) {
return BiometryStatus.AVAILABLE
}
// Hardware exists but no fingerprint/face is enrolled.
if (strongResult == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ||
weakResult == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
) {
return BiometryStatus.NOTENROLLED
}
// Permanently or contextually unavailable.
val unavailableCodes = setOf(
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED
)
if (strongResult in unavailableCodes || weakResult in unavailableCodes) {
return BiometryStatus.NOTAVAILABLE
}
return BiometryStatus.UNKNOWN
}
}