Skip to content

Commit 4182fc7

Browse files
authored
feat: password protection & hardware security added
* feat: password protection check added also added detection methods documentation Signed-off-by: Denis Dobanda <[email protected]> * feat: hardware security detection implemented Signed-off-by: Denis Dobanda <[email protected]> * chore: readme updated Signed-off-by: Denis Dobanda <[email protected]> * chore: pr review Signed-off-by: Denis Dobanda <[email protected]> --------- Signed-off-by: Denis Dobanda <[email protected]>
1 parent a510a9b commit 4182fc7

11 files changed

+170
-16
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ Already implemented Features are:
2121
- [x] Jailbreak or Root Detection
2222
- [x] Hooks Detection
2323
- [x] Simulator Detection
24+
- [x] Debugger Detection
25+
- [x] Device Passcode Check
26+
- [x] Hardware Security Check
2427

2528
You can see them in action with the [Example App](./SecurityToolkitExample) we've provided
2629

@@ -62,10 +65,8 @@ Use Async Stream API to get detected threats asynchronously:
6265

6366
Next features to be implemented:
6467
- [ ] App Signature Check
65-
- [ ] Debugger Detection
66-
- [ ] Device Passcode Check
6768
- [ ] Integrity Check
68-
- [ ] Hardware Security Check
69+
6970

7071
## Contributing
7172

SecurityToolkitExample/SecurityToolkitExample/Resources/en.lproj/Localizable.strings

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
"threat.simulator.title" = "Simulator";
77
"threat.simulator.description" = "Running the application in a simulator";
88
"threat.debugger.title" = "Debugger";
9-
"threat.debugger.description" = "A tool that allows developers to inspect and modify the execution of a program in real-time, potentially exposing sensitive data or allowing unauthorized control.";
9+
"threat.debugger.description" = "A tool that allows developers to inspect and modify the execution of a program in real-time, potentially exposing sensitive data or allowing unauthorized control";
10+
"threat.passcodeUnprotectedDevice.title" = "Passcode";
11+
"threat.passcodeUnprotectedDevice.description" = "Indicates if current device is unprotected with a passcode. Biometric protection requires a passcode to be set up";
12+
"threat.hardware.title" = "Hardware protection";
13+
"threat.hardware.description" = "Refers to hardware capabilities of current device, specific to Secure Enclave layer. If not available, no additional hardware security layer can be used when working with keys, certificates and keychain";
1014

1115
// MARK: Threat List
1216
"threat.list.title" = "Protection";

SecurityToolkitExample/SecurityToolkitExample/Sources/ThreatStatus.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,17 @@ struct ThreatStatus: Hashable {
2525
ThreatStatus(
2626
title: R.string.localizable.threatDebuggerTitle(),
2727
description: R.string.localizable.threatDebuggerDescription(),
28-
isDetected: ThreatDetectionCenter.isDebuggerDetected
28+
isDetected: ThreatDetectionCenter.isDebuggerDetected ?? false
29+
),
30+
ThreatStatus(
31+
title: R.string.localizable.threatPasscodeUnprotectedDeviceTitle(),
32+
description: R.string.localizable.threatPasscodeUnprotectedDeviceDescription(),
33+
isDetected: ThreatDetectionCenter.isDeviceWithoutPasscodeDetected
34+
),
35+
ThreatStatus(
36+
title: R.string.localizable.threatHardwareTitle(),
37+
description: R.string.localizable.threatHardwareDescription(),
38+
isDetected: ThreatDetectionCenter.isHardwareProtectionUnavailable
2939
),
3040
]
3141
}

Sources/ThreatDetectionCenter.swift

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,95 @@
11
import Foundation
22

33
public final class ThreatDetectionCenter {
4-
4+
5+
/// Will check if jailbreak is present
6+
///
7+
/// - Returns:
8+
/// `true`, if device is / was jailbroken;
9+
/// `false` otherwise
10+
///
11+
/// More about jailbreak: https://wikipedia.org/wiki/Jailbreak_%28iOS%29
12+
///
13+
/// > Should also detect jailbreak, even if the device is in a "safe" mode or
14+
/// jailbreak mode is not active / was not properly removed
515
public static var areRootPrivilegesDetected: Bool {
616
JailbreakDetection.threatDetected()
717
}
8-
18+
19+
/// Will check for an injection tool like Frida
20+
///
21+
/// - Returns:
22+
/// `true`, if dynamic hooks are loaded at the time;
23+
/// `false` otherwise
24+
///
25+
/// More: https://fingerprint.com/blog/exploring-frida-dynamic-instrumentation-tool-kit/
26+
///
27+
/// > By the nature of dynamic hooks, this checks should be made on a regular
28+
/// basis, given the attacker may chose to hook a function at a later time
29+
/// after the app started
30+
///
31+
/// > Important: with a sufficient reverse engineering skills, this check can
32+
/// be disabled. Use always in combination with another threats detections.
933
public static var areHooksDetected: Bool {
1034
HooksDetection.threatDetected()
1135
}
12-
36+
37+
/// Will check, if the app runs in a emulated / simulated environment
38+
///
39+
/// - Returns:
40+
/// `true`, if simulator environment is detected;
41+
/// `false` otherwise
1342
public static var isSimulatorDetected: Bool {
1443
SimulatorDetection.threatDetected()
1544
}
1645

17-
/// Will check if your application is being traced by a debugger.
46+
/// Will check, if the application is being traced by a debugger.
1847
///
1948
/// - Returns:
20-
/// `true`: If a debugger is detected.
21-
/// `false`: If no debugger is detected.
22-
/// `nil`: The detection process did not produce a definitive result. This could happen due to system limitations, lack of required permissions, or other undefined conditions.
49+
/// `true`, if a debugger is detected;
50+
/// `false`, if no debugger is detected;
51+
/// `nil`, if the detection process did not produce a definitive result.
52+
/// This could happen due to system limitations, lack of required
53+
/// permissions, or other undefined conditions.
2354
///
24-
/// A debugger is a tool that allows developers to inspect and modify the execution of a program in real-time, potentially exposing sensitive data or allowing unauthorized control.
55+
/// A debugger is a tool that allows developers to inspect and modify the
56+
/// execution of a program in real-time, potentially exposing sensitive data
57+
/// or allowing unauthorized control.
2558
///
26-
/// ## Notes
27-
/// Please note that Apple itself may require a debugger for the app review process.
59+
/// > Please note that Apple itself may require a debugger for the app review
60+
/// process.
2861
public static var isDebuggerDetected: Bool? {
2962
DebuggerDetection.threatDetected()
3063
}
64+
65+
/// Will check, if current device is protected with at least a passcode
66+
///
67+
/// - Returns:
68+
/// `true`, if device is unprotected;
69+
/// `false`, if device is protected with at least a passcode
70+
public static var isDeviceWithoutPasscodeDetected: Bool {
71+
DevicePasscodeDetection.threatDetected()
72+
}
73+
74+
/// Will check, if current device has hardware protection layer
75+
/// (Secure Enclave)
76+
///
77+
/// More: https://support.apple.com/en-us/guide/security/secf020d1074/web
78+
///
79+
/// More: https://developer.apple.com/documentation/security/protecting-keys-with-the-secure-enclave
80+
///
81+
/// - Returns:
82+
/// `true`, if device has no hardware protection;
83+
/// `false` otherwise
84+
///
85+
/// > Should be evaluated on a real device. Should only be used as an
86+
/// indicator, if current device is capable of hardware protection. Does not
87+
/// automatically mean, that encryption operations (keys, certificates,
88+
/// keychain) are always backed by hardware. You should make sure, such
89+
/// operations are implemented correctly with hardware layer
90+
public static var isHardwareProtectionUnavailable: Bool {
91+
HardwareSecurityDetection.threatDetected()
92+
}
3193

3294

3395
// MARK: - Async Threat Detection
@@ -38,6 +100,8 @@ public final class ThreatDetectionCenter {
38100
case hooks
39101
case simulator
40102
case debugger
103+
case deviceWithoutPasscode
104+
case hardwareProtectionUnavailable
41105
}
42106

43107
/// Stream that contains possible threats that could be detected
@@ -59,7 +123,15 @@ public final class ThreatDetectionCenter {
59123
if DebuggerDetection.threatDetected() ?? false {
60124
continuation.yield(.debugger)
61125
}
126+
127+
if DevicePasscodeDetection.threatDetected() {
128+
continuation.yield(.deviceWithoutPasscode)
129+
}
62130

131+
if HardwareSecurityDetection.threatDetected() {
132+
continuation.yield(.hardwareProtectionUnavailable)
133+
}
134+
63135
continuation.finish()
64136
}
65137
}

Sources/DebuggerDetection.swift renamed to Sources/internal/DebuggerDetection.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ fileprivate extension DebuggerDetection {
1515
/// if the process is traced
1616
private static func hasTracerFlagSet() -> Bool? {
1717
var info = kinfo_proc()
18-
var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] // Kernel info, process info, specific process by PID, get current process ID
18+
// Kernel info, process info, specific process by PID, get current process ID
19+
var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()]
1920
var size = MemoryLayout.stride(ofValue: info)
2021

2122
let unixStatusCode = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Foundation
2+
import LocalAuthentication
3+
4+
// MARK: - Internal
5+
internal final class DevicePasscodeDetection {
6+
7+
static func threatDetected() -> Bool {
8+
!hasDevicePasscode()
9+
}
10+
}
11+
12+
// MARK: - Private
13+
fileprivate extension DevicePasscodeDetection {
14+
15+
/// Will check if the user can perform authentication with a biometrics or
16+
/// a passcode
17+
private static func hasDevicePasscode() -> Bool {
18+
var error: NSError?
19+
let result = LAContext().canEvaluatePolicy(
20+
.deviceOwnerAuthentication,
21+
error: &error
22+
)
23+
if result {
24+
return true
25+
} else if let error {
26+
return error.code != LAError.passcodeNotSet.rawValue
27+
} else {
28+
return false
29+
}
30+
}
31+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Foundation
2+
import LocalAuthentication
3+
4+
// MARK: - Internal
5+
internal final class HardwareSecurityDetection {
6+
7+
static func threatDetected() -> Bool {
8+
!isSecureEnclaveAvailable()
9+
}
10+
}
11+
12+
// MARK: - Private
13+
fileprivate extension HardwareSecurityDetection {
14+
15+
/// See https://stackoverflow.com/a/49318485/7484013
16+
private static func isSecureEnclaveAvailable() -> Bool {
17+
var error: NSError?
18+
19+
/// Policies can have certain requirements which, when not satisfied,
20+
/// would always cause the policy evaluation to fail - e.g. a passcode
21+
/// set, a fingerprint enrolled with Touch ID or a face set up with
22+
/// Face ID. This method allows easy checking for such conditions.
23+
let result = LAContext().canEvaluatePolicy(
24+
.deviceOwnerAuthenticationWithBiometrics,
25+
error: &error
26+
)
27+
if result {
28+
return true
29+
} else if let error {
30+
return error.code != LAError.biometryNotAvailable.rawValue
31+
} else {
32+
return false
33+
}
34+
}
35+
}
File renamed without changes.

docs/1.png

-275 KB
Loading

0 commit comments

Comments
 (0)