@cap-kit/integrity 8.0.0-next.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapKitIntegrity.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +26 -0
- package/README.md +1104 -0
- package/android/build.gradle +104 -0
- package/android/src/main/AndroidManifest.xml +21 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityCheckOptions.kt +37 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityConfig.kt +59 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityError.kt +40 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityImpl.kt +319 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityPlugin.kt +475 -0
- package/android/src/main/java/io/capkit/integrity/IntegrityReportBuilder.kt +130 -0
- package/android/src/main/java/io/capkit/integrity/IntegritySignalBuilder.kt +72 -0
- package/android/src/main/java/io/capkit/integrity/emulator/IntegrityEmulatorChecks.kt +38 -0
- package/android/src/main/java/io/capkit/integrity/filesystem/IntegrityFilesystemChecks.kt +51 -0
- package/android/src/main/java/io/capkit/integrity/hook/IntegrityHookChecks.kt +61 -0
- package/android/src/main/java/io/capkit/integrity/remote/IntegrityRemoteAttestor.kt +49 -0
- package/android/src/main/java/io/capkit/integrity/root/IntegrityRootDetector.kt +136 -0
- package/android/src/main/java/io/capkit/integrity/runtime/IntegrityRuntimeChecks.kt +87 -0
- package/android/src/main/java/io/capkit/integrity/ui/IntegrityBlockActivity.kt +173 -0
- package/android/src/main/java/io/capkit/integrity/ui/IntegrityUISignals.kt +57 -0
- package/android/src/main/java/io/capkit/integrity/utils/IntegrityLogger.kt +85 -0
- package/android/src/main/java/io/capkit/integrity/utils/IntegrityUtils.kt +105 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/android/src/main/res/values/styles.xml +5 -0
- package/dist/docs.json +598 -0
- package/dist/esm/definitions.d.ts +554 -0
- package/dist/esm/definitions.js +56 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +32 -0
- package/dist/esm/web.js +51 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +130 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +133 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/IntegrityPlugin/IntegrityCheckOptions.swift +41 -0
- package/ios/Sources/IntegrityPlugin/IntegrityConfig.swift +135 -0
- package/ios/Sources/IntegrityPlugin/IntegrityEntitlementChecks.swift +58 -0
- package/ios/Sources/IntegrityPlugin/IntegrityError.swift +49 -0
- package/ios/Sources/IntegrityPlugin/IntegrityImpl.swift +397 -0
- package/ios/Sources/IntegrityPlugin/IntegrityPlugin.swift +345 -0
- package/ios/Sources/IntegrityPlugin/IntegrityReportBuilder.swift +184 -0
- package/ios/Sources/IntegrityPlugin/Utils/IntegrityLogger.swift +69 -0
- package/ios/Sources/IntegrityPlugin/Utils/IntegrityUtils.swift +144 -0
- package/ios/Sources/IntegrityPlugin/Version.swift +16 -0
- package/ios/Sources/IntegrityPlugin/filesystem/IntegrityFilesystemChecks.swift +86 -0
- package/ios/Sources/IntegrityPlugin/hook/IntegrityHookChecks.swift +85 -0
- package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakDetector.swift +74 -0
- package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakUrlSchemeDetector.swift +42 -0
- package/ios/Sources/IntegrityPlugin/remote/IntegrityRemoteAttestor.swift +40 -0
- package/ios/Sources/IntegrityPlugin/runtime/IntegrityRuntimeChecks.swift +63 -0
- package/ios/Sources/IntegrityPlugin/simulator/IntegritySimulatorChecks.swift +20 -0
- package/ios/Sources/IntegrityPlugin/ui/IntegrityBlockViewController.swift +143 -0
- package/ios/Tests/IntegrityPluginTests/IntegrityPluginTests.swift +10 -0
- package/package.json +106 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import MachO
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
Native iOS implementation for the Integrity plugin.
|
|
6
|
+
*/
|
|
7
|
+
@objc
|
|
8
|
+
public final class IntegrityImpl: NSObject {
|
|
9
|
+
|
|
10
|
+
// MARK: - Configuration
|
|
11
|
+
|
|
12
|
+
/// Immutable plugin configuration injected by the Plugin layer.
|
|
13
|
+
private var config: IntegrityConfig?
|
|
14
|
+
|
|
15
|
+
/// Buffer for integrity signals captured during early app boot.
|
|
16
|
+
/// Flushed on the first explicit integrity check.
|
|
17
|
+
private static var bootSignals: [[String: Any]] = []
|
|
18
|
+
|
|
19
|
+
// Negative cache for expensive integrity checks.
|
|
20
|
+
// Caches only "no-signal" results for a short time window.
|
|
21
|
+
private struct NegativeCacheEntry {
|
|
22
|
+
let timestampMs: Int
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private var negativeCache: [String: NegativeCacheEntry] = [:]
|
|
26
|
+
|
|
27
|
+
private let negativeCacheTTLms = 30_000
|
|
28
|
+
|
|
29
|
+
// MARK: - Early Boot Hooks
|
|
30
|
+
|
|
31
|
+
/// Entry point invoked from AppDelegate during application launch.
|
|
32
|
+
@objc public static func onAppLaunch() {
|
|
33
|
+
// Capture jailbreak-related filesystem signals immediately at boot.
|
|
34
|
+
let signals = IntegrityJailbreakDetector.detect(includeDebug: false)
|
|
35
|
+
self.bootSignals.append(contentsOf: signals)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MARK: - Configuration Injection
|
|
39
|
+
|
|
40
|
+
/// Applies static plugin configuration.
|
|
41
|
+
func applyConfig(_ config: IntegrityConfig) {
|
|
42
|
+
precondition(
|
|
43
|
+
self.config == nil,
|
|
44
|
+
"IntegrityImpl.applyConfig(_:) must be called exactly once"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.config = config
|
|
48
|
+
IntegrityLogger.verbose = config.verboseLogging
|
|
49
|
+
|
|
50
|
+
IntegrityLogger.debug(
|
|
51
|
+
"Integrity configuration applied. Verbose logging:",
|
|
52
|
+
config.verboseLogging
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// MARK: - Remote Attestation (Stub)
|
|
57
|
+
|
|
58
|
+
/// Placeholder for future Apple App Attest integration.
|
|
59
|
+
func getAppAttestSignal(options: IntegrityCheckOptions) -> [String: Any]? {
|
|
60
|
+
return IntegrityRemoteAttestor.getAppAttestSignal(options: options)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// MARK: - Integrity Check Orchestration
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
Executes integrity checks according to the requested options
|
|
67
|
+
and returns a fully assembled integrity report.
|
|
68
|
+
*/
|
|
69
|
+
func performCheck(
|
|
70
|
+
options: IntegrityCheckOptions
|
|
71
|
+
) throws -> [String: Any] {
|
|
72
|
+
|
|
73
|
+
// Apply negative cache only for standard / strict levels.
|
|
74
|
+
// Cached results represent a recent "no-signal" execution.
|
|
75
|
+
if let level = options.level,
|
|
76
|
+
level != "basic",
|
|
77
|
+
isNegativeCacheValid(level: level) {
|
|
78
|
+
|
|
79
|
+
IntegrityLogger.debug(
|
|
80
|
+
"Negative cache hit for integrity check:",
|
|
81
|
+
level
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
return IntegrityReportBuilder.buildReport(
|
|
85
|
+
signals: [],
|
|
86
|
+
isEmulator: false,
|
|
87
|
+
platform: "ios"
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
var signals: [[String: Any]] = []
|
|
92
|
+
let includeDebug = options.includeDebugInfo ?? false
|
|
93
|
+
|
|
94
|
+
mergeBootSignals(into: &signals)
|
|
95
|
+
|
|
96
|
+
let isSimulator = runChecks(
|
|
97
|
+
options: options,
|
|
98
|
+
signals: &signals,
|
|
99
|
+
includeDebug: includeDebug
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
appendCorrelations(
|
|
103
|
+
signals: &signals,
|
|
104
|
+
includeDebug: includeDebug
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
// Update negative cache only when no integrity signals are detected.
|
|
108
|
+
// Any detected signal invalidates the cached clean state.
|
|
109
|
+
if let level = options.level, level != "basic" {
|
|
110
|
+
if signals.isEmpty {
|
|
111
|
+
updateNegativeCache(level: level)
|
|
112
|
+
} else {
|
|
113
|
+
clearNegativeCache(level: level)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return IntegrityReportBuilder.buildReport(
|
|
118
|
+
signals: signals,
|
|
119
|
+
isEmulator: isSimulator,
|
|
120
|
+
platform: "ios"
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// MARK: - BASIC Checks
|
|
125
|
+
|
|
126
|
+
/// Executes BASIC integrity checks.
|
|
127
|
+
private func performBasicChecks(
|
|
128
|
+
signals: inout [[String: Any]],
|
|
129
|
+
includeDebug: Bool
|
|
130
|
+
) -> Bool {
|
|
131
|
+
|
|
132
|
+
appendFilesystemJailbreakSignals(
|
|
133
|
+
to: &signals,
|
|
134
|
+
includeDebug: includeDebug
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
let isSimulator = appendSimulatorSignalIfNeeded(
|
|
138
|
+
to: &signals,
|
|
139
|
+
includeDebug: includeDebug
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
appendUrlSchemeJailbreakSignalIfNeeded(
|
|
143
|
+
to: &signals,
|
|
144
|
+
includeDebug: includeDebug
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return isSimulator
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// MARK: - STANDARD / STRICT Checks
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
Executes STANDARD and STRICT integrity checks,
|
|
154
|
+
including runtime and instrumentation heuristics.
|
|
155
|
+
*/
|
|
156
|
+
private func performStandardChecks(
|
|
157
|
+
signals: inout [[String: Any]],
|
|
158
|
+
includeDebug: Bool
|
|
159
|
+
) {
|
|
160
|
+
|
|
161
|
+
signals.append(
|
|
162
|
+
contentsOf: IntegrityRuntimeChecks.debugSignals(
|
|
163
|
+
includeDebug: includeDebug
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
let hookingDetected =
|
|
168
|
+
IntegrityHookChecks.hookingSignal(
|
|
169
|
+
includeDebug: includeDebug
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if let hookingDetected {
|
|
173
|
+
signals.append(hookingDetected)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let portDetected =
|
|
177
|
+
IntegrityHookChecks.isFridaPortOpen()
|
|
178
|
+
|
|
179
|
+
if portDetected {
|
|
180
|
+
signals.append(
|
|
181
|
+
IntegrityUtils.buildSignal(
|
|
182
|
+
id: "ios_frida_port_detected",
|
|
183
|
+
category: "hook",
|
|
184
|
+
confidence: "medium",
|
|
185
|
+
description: "Known instrumentation service port is reachable on localhost",
|
|
186
|
+
metadata: ["port": 27042],
|
|
187
|
+
includeDebug: includeDebug
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if hookingDetected != nil && portDetected {
|
|
193
|
+
signals.append(
|
|
194
|
+
IntegrityUtils.buildSignal(
|
|
195
|
+
id: "ios_frida_correlation_confirmed",
|
|
196
|
+
category: "hook",
|
|
197
|
+
confidence: "high",
|
|
198
|
+
description: "Multiple instrumentation indicators detected simultaneously",
|
|
199
|
+
metadata: ["source": "library+port"],
|
|
200
|
+
includeDebug: includeDebug
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// MARK: - Orchestration Helpers
|
|
207
|
+
|
|
208
|
+
/// Merges early boot signals into the current execution context.
|
|
209
|
+
private func mergeBootSignals(
|
|
210
|
+
into signals: inout [[String: Any]]
|
|
211
|
+
) {
|
|
212
|
+
signals.append(contentsOf: IntegrityImpl.bootSignals)
|
|
213
|
+
IntegrityImpl.bootSignals.removeAll()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Runs integrity checks according to the selected strictness level.
|
|
217
|
+
private func runChecks(
|
|
218
|
+
options: IntegrityCheckOptions,
|
|
219
|
+
signals: inout [[String: Any]],
|
|
220
|
+
includeDebug: Bool
|
|
221
|
+
) -> Bool {
|
|
222
|
+
|
|
223
|
+
let isSimulator = performBasicChecks(
|
|
224
|
+
signals: &signals,
|
|
225
|
+
includeDebug: includeDebug
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
if (options.level ?? "basic") != "basic" {
|
|
229
|
+
performStandardChecks(
|
|
230
|
+
signals: &signals,
|
|
231
|
+
includeDebug: includeDebug
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
// Entitlement & Provisioning Verification (RASP)
|
|
235
|
+
// Added check to verify if the production app allows debugging via entitlements
|
|
236
|
+
if let entData = IntegrityEntitlementChecks.checkEntitlements() {
|
|
237
|
+
if let isDebuggable = entData["debuggable"] as? Bool, isDebuggable, options.level == "strict" {
|
|
238
|
+
signals.append(
|
|
239
|
+
IntegrityUtils.buildSignal(
|
|
240
|
+
id: "ios_entitlement_debuggable",
|
|
241
|
+
category: "tamper",
|
|
242
|
+
confidence: "high",
|
|
243
|
+
description: "Production app has 'get-task-allow' enabled in provisioning profile",
|
|
244
|
+
metadata: entData,
|
|
245
|
+
includeDebug: includeDebug
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if let hasKeychain = entData["has_keychain_access"] as? Bool, !hasKeychain, options.level == "strict" {
|
|
251
|
+
signals.append(
|
|
252
|
+
IntegrityUtils.buildSignal(
|
|
253
|
+
id: "ios_keychain_entitlement_missing",
|
|
254
|
+
category: "tamper",
|
|
255
|
+
confidence: "medium",
|
|
256
|
+
description: "Expected keychain-access-groups are missing from provisioning profile",
|
|
257
|
+
metadata: entData,
|
|
258
|
+
includeDebug: includeDebug
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if options.level == "strict",
|
|
266
|
+
let attest = getAppAttestSignal(options: options) {
|
|
267
|
+
signals.append(attest)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return isSimulator
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// Appends derived correlation signals based on collected indicators.
|
|
274
|
+
private func appendCorrelations(
|
|
275
|
+
signals: inout [[String: Any]],
|
|
276
|
+
includeDebug: Bool
|
|
277
|
+
) {
|
|
278
|
+
if let jailbreakCorrelation =
|
|
279
|
+
IntegrityCorrelationUtils.jailbreakCorrelation(
|
|
280
|
+
from: signals,
|
|
281
|
+
includeDebug: includeDebug
|
|
282
|
+
) {
|
|
283
|
+
signals.append(jailbreakCorrelation)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if let jailbreakAndHookCorrelation =
|
|
287
|
+
IntegrityCorrelationUtils.jailbreakAndHookCorrelation(
|
|
288
|
+
from: signals,
|
|
289
|
+
includeDebug: includeDebug
|
|
290
|
+
) {
|
|
291
|
+
signals.append(jailbreakAndHookCorrelation)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// MARK: - BASIC Check Helpers
|
|
296
|
+
|
|
297
|
+
/// Appends filesystem-based jailbreak indicators.
|
|
298
|
+
private func appendFilesystemJailbreakSignals(
|
|
299
|
+
to signals: inout [[String: Any]],
|
|
300
|
+
includeDebug: Bool
|
|
301
|
+
) {
|
|
302
|
+
signals.append(
|
|
303
|
+
contentsOf: IntegrityJailbreakDetector.detect(
|
|
304
|
+
includeDebug: includeDebug
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if IntegrityFilesystemChecks.canEscapeSandbox() {
|
|
309
|
+
signals.append(
|
|
310
|
+
IntegrityUtils.buildSignal(
|
|
311
|
+
id: "ios_sandbox_escaped",
|
|
312
|
+
category: "tamper",
|
|
313
|
+
confidence: "high",
|
|
314
|
+
description: "Successfully wrote to a protected system directory (Sandbox violation)",
|
|
315
|
+
metadata: ["path": "/private/integrity_test.txt"],
|
|
316
|
+
includeDebug: includeDebug
|
|
317
|
+
)
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if IntegrityFilesystemChecks.hasSuspiciousSymlinks() {
|
|
322
|
+
signals.append(
|
|
323
|
+
IntegrityUtils.buildSignal(
|
|
324
|
+
id: "ios_suspicious_symlink",
|
|
325
|
+
category: "jailbreak",
|
|
326
|
+
confidence: "high",
|
|
327
|
+
description: "System directories are redirected via symbolic links",
|
|
328
|
+
includeDebug: includeDebug
|
|
329
|
+
)
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/// Appends simulator signal if the app is running in a simulator.
|
|
335
|
+
private func appendSimulatorSignalIfNeeded(
|
|
336
|
+
to signals: inout [[String: Any]],
|
|
337
|
+
includeDebug: Bool
|
|
338
|
+
) -> Bool {
|
|
339
|
+
|
|
340
|
+
let isSimulator = IntegritySimulatorChecks.isSimulator()
|
|
341
|
+
|
|
342
|
+
if isSimulator {
|
|
343
|
+
signals.append(
|
|
344
|
+
IntegrityUtils.buildSignal(
|
|
345
|
+
id: "ios_simulator",
|
|
346
|
+
category: "emulator",
|
|
347
|
+
confidence: "high",
|
|
348
|
+
description: "Application is running in an iOS simulator environment",
|
|
349
|
+
metadata: ["type": "apple_simulator"],
|
|
350
|
+
includeDebug: includeDebug
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return isSimulator
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// Appends optional jailbreak URL scheme detection signal.
|
|
359
|
+
private func appendUrlSchemeJailbreakSignalIfNeeded(
|
|
360
|
+
to signals: inout [[String: Any]],
|
|
361
|
+
includeDebug: Bool
|
|
362
|
+
) {
|
|
363
|
+
guard
|
|
364
|
+
let schemeConfig = config?.jailbreakUrlSchemes,
|
|
365
|
+
schemeConfig.enabled,
|
|
366
|
+
let schemeSignal =
|
|
367
|
+
IntegrityJailbreakUrlSchemeDetector.detect(
|
|
368
|
+
schemes: schemeConfig.schemes,
|
|
369
|
+
includeDebug: includeDebug
|
|
370
|
+
)
|
|
371
|
+
else { return }
|
|
372
|
+
|
|
373
|
+
signals.append(schemeSignal)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private func cacheKey(level: String) -> String {
|
|
377
|
+
return "ios:\(level)"
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private func isNegativeCacheValid(level: String) -> Bool {
|
|
381
|
+
guard let entry = negativeCache[cacheKey(level: level)] else {
|
|
382
|
+
return false
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let now = Int(Date().timeIntervalSince1970 * 1000)
|
|
386
|
+
return now - entry.timestampMs <= negativeCacheTTLms
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private func updateNegativeCache(level: String) {
|
|
390
|
+
let now = Int(Date().timeIntervalSince1970 * 1000)
|
|
391
|
+
negativeCache[cacheKey(level: level)] = NegativeCacheEntry(timestampMs: now)
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private func clearNegativeCache(level: String) {
|
|
395
|
+
negativeCache.removeValue(forKey: cacheKey(level: level))
|
|
396
|
+
}
|
|
397
|
+
}
|