@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.
Files changed (59) hide show
  1. package/CapKitIntegrity.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +26 -0
  4. package/README.md +1104 -0
  5. package/android/build.gradle +104 -0
  6. package/android/src/main/AndroidManifest.xml +21 -0
  7. package/android/src/main/java/io/capkit/integrity/IntegrityCheckOptions.kt +37 -0
  8. package/android/src/main/java/io/capkit/integrity/IntegrityConfig.kt +59 -0
  9. package/android/src/main/java/io/capkit/integrity/IntegrityError.kt +40 -0
  10. package/android/src/main/java/io/capkit/integrity/IntegrityImpl.kt +319 -0
  11. package/android/src/main/java/io/capkit/integrity/IntegrityPlugin.kt +475 -0
  12. package/android/src/main/java/io/capkit/integrity/IntegrityReportBuilder.kt +130 -0
  13. package/android/src/main/java/io/capkit/integrity/IntegritySignalBuilder.kt +72 -0
  14. package/android/src/main/java/io/capkit/integrity/emulator/IntegrityEmulatorChecks.kt +38 -0
  15. package/android/src/main/java/io/capkit/integrity/filesystem/IntegrityFilesystemChecks.kt +51 -0
  16. package/android/src/main/java/io/capkit/integrity/hook/IntegrityHookChecks.kt +61 -0
  17. package/android/src/main/java/io/capkit/integrity/remote/IntegrityRemoteAttestor.kt +49 -0
  18. package/android/src/main/java/io/capkit/integrity/root/IntegrityRootDetector.kt +136 -0
  19. package/android/src/main/java/io/capkit/integrity/runtime/IntegrityRuntimeChecks.kt +87 -0
  20. package/android/src/main/java/io/capkit/integrity/ui/IntegrityBlockActivity.kt +173 -0
  21. package/android/src/main/java/io/capkit/integrity/ui/IntegrityUISignals.kt +57 -0
  22. package/android/src/main/java/io/capkit/integrity/utils/IntegrityLogger.kt +85 -0
  23. package/android/src/main/java/io/capkit/integrity/utils/IntegrityUtils.kt +105 -0
  24. package/android/src/main/res/.gitkeep +0 -0
  25. package/android/src/main/res/values/styles.xml +5 -0
  26. package/dist/docs.json +598 -0
  27. package/dist/esm/definitions.d.ts +554 -0
  28. package/dist/esm/definitions.js +56 -0
  29. package/dist/esm/definitions.js.map +1 -0
  30. package/dist/esm/index.d.ts +15 -0
  31. package/dist/esm/index.js +16 -0
  32. package/dist/esm/index.js.map +1 -0
  33. package/dist/esm/web.d.ts +32 -0
  34. package/dist/esm/web.js +51 -0
  35. package/dist/esm/web.js.map +1 -0
  36. package/dist/plugin.cjs.js +130 -0
  37. package/dist/plugin.cjs.js.map +1 -0
  38. package/dist/plugin.js +133 -0
  39. package/dist/plugin.js.map +1 -0
  40. package/ios/Sources/IntegrityPlugin/IntegrityCheckOptions.swift +41 -0
  41. package/ios/Sources/IntegrityPlugin/IntegrityConfig.swift +135 -0
  42. package/ios/Sources/IntegrityPlugin/IntegrityEntitlementChecks.swift +58 -0
  43. package/ios/Sources/IntegrityPlugin/IntegrityError.swift +49 -0
  44. package/ios/Sources/IntegrityPlugin/IntegrityImpl.swift +397 -0
  45. package/ios/Sources/IntegrityPlugin/IntegrityPlugin.swift +345 -0
  46. package/ios/Sources/IntegrityPlugin/IntegrityReportBuilder.swift +184 -0
  47. package/ios/Sources/IntegrityPlugin/Utils/IntegrityLogger.swift +69 -0
  48. package/ios/Sources/IntegrityPlugin/Utils/IntegrityUtils.swift +144 -0
  49. package/ios/Sources/IntegrityPlugin/Version.swift +16 -0
  50. package/ios/Sources/IntegrityPlugin/filesystem/IntegrityFilesystemChecks.swift +86 -0
  51. package/ios/Sources/IntegrityPlugin/hook/IntegrityHookChecks.swift +85 -0
  52. package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakDetector.swift +74 -0
  53. package/ios/Sources/IntegrityPlugin/jailbreak/IntegrityJailbreakUrlSchemeDetector.swift +42 -0
  54. package/ios/Sources/IntegrityPlugin/remote/IntegrityRemoteAttestor.swift +40 -0
  55. package/ios/Sources/IntegrityPlugin/runtime/IntegrityRuntimeChecks.swift +63 -0
  56. package/ios/Sources/IntegrityPlugin/simulator/IntegritySimulatorChecks.swift +20 -0
  57. package/ios/Sources/IntegrityPlugin/ui/IntegrityBlockViewController.swift +143 -0
  58. package/ios/Tests/IntegrityPluginTests/IntegrityPluginTests.swift +10 -0
  59. package/package.json +106 -0
@@ -0,0 +1,51 @@
1
+ package io.capkit.integrity.filesystem
2
+
3
+ import io.capkit.integrity.IntegrityCheckOptions
4
+ import io.capkit.integrity.IntegritySignalBuilder
5
+ import java.io.File
6
+
7
+ /**
8
+ * Performs filesystem integrity checks related to sandbox escape
9
+ * and suspicious directory permissions.
10
+ */
11
+ object IntegrityFilesystemChecks {
12
+ /**
13
+ * Attempts to detect sandbox escape by checking write access
14
+ * to protected system locations without performing mutations.
15
+ *
16
+ * NOTE:
17
+ * - This check is best-effort and heuristic-based
18
+ * - No filesystem writes are performed (read-only probes)
19
+ */
20
+ fun checkSandboxEscape(options: IntegrityCheckOptions): Map<String, Any>? {
21
+ val protectedPaths =
22
+ listOf(
23
+ "/system",
24
+ "/system/bin",
25
+ "/system/xbin",
26
+ )
27
+
28
+ return try {
29
+ for (path in protectedPaths) {
30
+ val file = File(path)
31
+ if (file.exists() && file.canWrite()) {
32
+ return IntegritySignalBuilder.build(
33
+ id = "android_sandbox_escaped",
34
+ category = "tamper",
35
+ confidence = "high",
36
+ description = "Write access detected on protected system directory",
37
+ metadata = mapOf("path" to path),
38
+ options = options,
39
+ )
40
+ }
41
+ }
42
+ null
43
+ } catch (_: SecurityException) {
44
+ // Access denied → expected in a secure environment
45
+ null
46
+ } catch (_: Exception) {
47
+ // Expected failure in a secure environment
48
+ null
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,61 @@
1
+ package io.capkit.integrity.hook
2
+
3
+ import io.capkit.integrity.IntegrityError
4
+ import java.io.File
5
+ import java.net.InetSocketAddress
6
+ import java.net.Socket
7
+
8
+ /**
9
+ * Detects instrumentation frameworks (like Frida) via memory inspection
10
+ * and local network port scanning.
11
+ */
12
+ object IntegrityHookChecks {
13
+ /**
14
+ * Frida detection via process memory map inspection.
15
+ * Looks for known library artifacts in /proc/self/maps.
16
+ */
17
+ fun checkFridaMemory(): Boolean {
18
+ return try {
19
+ val mapsFile = File("/proc/self/maps")
20
+ if (mapsFile.exists()) {
21
+ mapsFile.useLines { lines ->
22
+ lines.any { it.contains("frida", ignoreCase = true) || it.contains("gadget", ignoreCase = true) }
23
+ }
24
+ } else {
25
+ false
26
+ }
27
+ } catch (e: SecurityException) {
28
+ // Swallowed to allow other checks to complete
29
+ false
30
+ } catch (_: Exception) {
31
+ false
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Detects known Frida server ports on localhost.
37
+ *
38
+ * @throws IntegrityError.Unavailable If socket access is restricted.
39
+ */
40
+ fun checkFridaPorts(): Boolean {
41
+ val ports = listOf(27042, 27043)
42
+ val timeoutMs = 1000
43
+
44
+ return ports.any { port ->
45
+ try {
46
+ Socket().use { socket ->
47
+ // Use a timeout to prevent long-running blocking calls
48
+ socket.connect(InetSocketAddress("127.0.0.1", port), timeoutMs)
49
+ true
50
+ }
51
+ } catch (e: SecurityException) {
52
+ throw IntegrityError.Unavailable(
53
+ "Socket access denied while checking Frida ports.",
54
+ )
55
+ } catch (_: Exception) {
56
+ // Connection failed or timed out, which is expected in clean environments
57
+ false
58
+ }
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,49 @@
1
+ package io.capkit.integrity.remote
2
+
3
+ import android.content.Context
4
+ import io.capkit.integrity.IntegrityCheckOptions
5
+
6
+ /**
7
+ * Handles remote attestation signals (Play Integrity API).
8
+ *
9
+ * IMPORTANT:
10
+ * - Play Integrity is NOT implemented yet.
11
+ * - Unavailability is reported explicitly.
12
+ * - The emitted signal is observational only and LOW confidence.
13
+ */
14
+ object IntegrityRemoteAttestor {
15
+ /**
16
+ * Returns a LOW confidence signal indicating that Play Integrity
17
+ * attestation is not implemented or not available.
18
+ *
19
+ * The signal is emitted only when strict mode is requested.
20
+ */
21
+ fun getPlayIntegritySignal(
22
+ context: Context,
23
+ options: IntegrityCheckOptions,
24
+ ): Map<String, Any>? {
25
+ if (options.level != "strict") {
26
+ return null
27
+ }
28
+
29
+ val signal =
30
+ mutableMapOf<String, Any>(
31
+ "id" to "android_play_integrity_unavailable",
32
+ "category" to "environment",
33
+ "confidence" to "low",
34
+ "metadata" to
35
+ mapOf(
36
+ "attestation" to "unsupported",
37
+ "provider" to "play_integrity",
38
+ "reason" to "not_implemented",
39
+ ),
40
+ )
41
+
42
+ if (options.includeDebugInfo) {
43
+ signal["description"] =
44
+ "Google Play Integrity attestation is not implemented or not available"
45
+ }
46
+
47
+ return signal
48
+ }
49
+ }
@@ -0,0 +1,136 @@
1
+ package io.capkit.integrity.root
2
+
3
+ import android.content.Context
4
+ import android.content.pm.PackageManager
5
+ import android.os.Build
6
+ import io.capkit.integrity.IntegrityCheckOptions
7
+ import io.capkit.integrity.IntegrityError
8
+ import io.capkit.integrity.IntegritySignalBuilder
9
+ import java.io.File
10
+
11
+ /**
12
+ * Performs root detection using filesystem heuristics and package inspection.
13
+ */
14
+ object IntegrityRootDetector {
15
+ private val suPaths =
16
+ listOf(
17
+ "/system/bin/su", "/system/xbin/su", "/sbin/su",
18
+ "/system/app/Superuser.apk", "/system/app/Superuser/Superuser.apk",
19
+ "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
20
+ "/su/bin/su", "/magisk/.core/bin/su", "/system/usr/we-need-root/su-backup/su",
21
+ "/system/bin/.ext/.su/su", "/system/bin/failsafe/su", "/data/local/su",
22
+ )
23
+
24
+ private val rootPackages =
25
+ listOf(
26
+ "com.noshufou.android.su",
27
+ "com.thirdparty.superuser",
28
+ "eu.chainfire.supersu",
29
+ "com.koushikdutta.superuser",
30
+ "com.zachareew.systemuituner",
31
+ "com.topjohnwu.magisk",
32
+ "com.alephzain.framaroot",
33
+ "org.adaway",
34
+ )
35
+
36
+ /**
37
+ * Cache process-lifetime for deterministic root signals.
38
+ *
39
+ * IMPORTANT:
40
+ * - Used ONLY for runtime checks
41
+ * - Boot-time checks MUST bypass this cache
42
+ */
43
+ private var cachedRootSignals: List<Map<String, Any>>? = null
44
+
45
+ /**
46
+ * Performs root detection using filesystem heuristics and build metadata.
47
+ *
48
+ * @param allowCache
49
+ * - true → reuse cached signals if available (runtime path)
50
+ * - false → force fresh detection (boot path)
51
+ */
52
+ fun checkRootSignals(
53
+ options: IntegrityCheckOptions,
54
+ allowCache: Boolean = true,
55
+ ): List<Map<String, Any>> {
56
+ if (allowCache) {
57
+ cachedRootSignals?.let { return it }
58
+ }
59
+
60
+ val signals = mutableListOf<Map<String, Any>>()
61
+
62
+ try {
63
+ for (path in suPaths) {
64
+ if (File(path).exists()) {
65
+ signals.add(
66
+ IntegritySignalBuilder.build(
67
+ id = "android_root_su",
68
+ category = "root",
69
+ confidence = "high",
70
+ description = "Presence of su binary detected in system paths",
71
+ metadata = mapOf("path" to path),
72
+ options = options,
73
+ ),
74
+ )
75
+ break
76
+ }
77
+ }
78
+ } catch (e: SecurityException) {
79
+ throw IntegrityError.Unavailable("Filesystem access denied while performing root checks.")
80
+ }
81
+
82
+ if (Build.TAGS?.contains("test-keys") == true) {
83
+ signals.add(
84
+ IntegritySignalBuilder.build(
85
+ id = "android_test_keys",
86
+ category = "root",
87
+ confidence = "medium",
88
+ description = "Device build signed with test keys",
89
+ metadata = mapOf("tags" to Build.TAGS),
90
+ options = options,
91
+ ),
92
+ )
93
+ }
94
+
95
+ if (allowCache) {
96
+ cachedRootSignals = signals
97
+ }
98
+
99
+ return signals
100
+ }
101
+
102
+ /**
103
+ * Checks for the presence of known root management applications.
104
+ */
105
+ fun checkRootPackages(
106
+ context: Context,
107
+ options: IntegrityCheckOptions,
108
+ ): List<Map<String, Any>> {
109
+ val signals = mutableListOf<Map<String, Any>>()
110
+ val pm = context.packageManager
111
+
112
+ for (pkg in rootPackages) {
113
+ try {
114
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
115
+ pm.getPackageInfo(pkg, PackageManager.PackageInfoFlags.of(0))
116
+ } else {
117
+ @Suppress("DEPRECATION")
118
+ pm.getPackageInfo(pkg, 0)
119
+ }
120
+
121
+ signals.add(
122
+ IntegritySignalBuilder.build(
123
+ id = "android_root_package",
124
+ category = "root",
125
+ confidence = "high",
126
+ description = "Detected known root management or related application",
127
+ metadata = mapOf("package" to pkg),
128
+ options = options,
129
+ ),
130
+ )
131
+ } catch (_: PackageManager.NameNotFoundException) {
132
+ }
133
+ }
134
+ return signals
135
+ }
136
+ }
@@ -0,0 +1,87 @@
1
+ package io.capkit.integrity.runtime
2
+
3
+ import android.content.Context
4
+ import android.content.pm.PackageManager
5
+ import android.os.Debug
6
+ import io.capkit.integrity.IntegrityCheckOptions
7
+ import io.capkit.integrity.IntegrityError
8
+ import io.capkit.integrity.IntegritySignalBuilder
9
+
10
+ /**
11
+ * Runtime integrity checks related to debugging conditions
12
+ * and application signing integrity.
13
+ */
14
+ object IntegrityRuntimeChecks {
15
+ /**
16
+ * Detects debugging conditions such as attached debuggers or debuggable flags.
17
+ */
18
+ fun checkDebugSignals(
19
+ context: Context,
20
+ options: IntegrityCheckOptions,
21
+ ): List<Map<String, Any>> {
22
+ val debugSignals = mutableListOf<Map<String, Any>>()
23
+ val isDebuggerConnected = Debug.isDebuggerConnected()
24
+ val isDebuggable = (context.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) != 0
25
+
26
+ if (isDebuggerConnected) {
27
+ debugSignals.add(
28
+ IntegritySignalBuilder.build(
29
+ id = "android_debugger_attached",
30
+ category = "debug",
31
+ confidence = "high",
32
+ description = "A debugger is currently attached to the running process",
33
+ metadata = mapOf("method" to "Debug.isDebuggerConnected"),
34
+ options = options,
35
+ ),
36
+ )
37
+ }
38
+
39
+ if (isDebuggable) {
40
+ debugSignals.add(
41
+ IntegritySignalBuilder.build(
42
+ id = "android_runtime_debuggable",
43
+ category = "debug",
44
+ confidence = "medium",
45
+ description = "Process is debuggable at runtime",
46
+ metadata = mapOf("flag" to "FLAG_DEBUGGABLE"),
47
+ options = options,
48
+ ),
49
+ )
50
+ }
51
+
52
+ return debugSignals
53
+ }
54
+
55
+ /**
56
+ * Performs a basic application signature integrity check.
57
+ */
58
+ fun checkAppSignature(context: Context): Boolean {
59
+ return try {
60
+ val packageInfo =
61
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
62
+ context.packageManager.getPackageInfo(
63
+ context.packageName,
64
+ PackageManager.GET_SIGNING_CERTIFICATES,
65
+ )
66
+ } else {
67
+ @Suppress("DEPRECATION")
68
+ context.packageManager.getPackageInfo(
69
+ context.packageName,
70
+ PackageManager.GET_SIGNATURES,
71
+ )
72
+ }
73
+
74
+ val signingInfo =
75
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
76
+ packageInfo.signingInfo?.apkContentsSigners
77
+ } else {
78
+ @Suppress("DEPRECATION")
79
+ packageInfo.signatures
80
+ }
81
+
82
+ signingInfo?.isNotEmpty() == true
83
+ } catch (e: Exception) {
84
+ throw IntegrityError.InitFailed("Failed to read application signing information.")
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,173 @@
1
+ package io.capkit.integrity.ui
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.os.Build
5
+ import android.os.Bundle
6
+ import android.webkit.WebView
7
+ import android.widget.LinearLayout
8
+ import androidx.appcompat.app.AppCompatActivity
9
+ import androidx.appcompat.widget.Toolbar
10
+ import java.io.BufferedReader
11
+ import java.io.InputStreamReader
12
+
13
+ /**
14
+ * Dedicated activity used to present the integrity block page.
15
+ *
16
+ * Responsibilities:
17
+ * - Display a developer-provided HTML block page
18
+ * - Support query parameters (e.g. "reason")
19
+ * - Optionally allow dismissal via native UI controls
20
+ *
21
+ * Security note:
22
+ * - The block page is NOT dismissible by default
23
+ * - Dismissal must be explicitly enabled by the host application
24
+ */
25
+ class IntegrityBlockActivity : AppCompatActivity() {
26
+ /**
27
+ * Whether the block page can be dismissed by the user.
28
+ *
29
+ * Defaults to false (secure-by-default).
30
+ */
31
+ private var dismissible: Boolean = false
32
+
33
+ @SuppressLint("SetJavaScriptEnabled")
34
+ override fun onCreate(savedInstanceState: Bundle?) {
35
+ super.onCreate(savedInstanceState)
36
+
37
+ // -------------------------------------------------------------------------
38
+ // Read options from Intent
39
+ // -------------------------------------------------------------------------
40
+
41
+ dismissible = intent.getBooleanExtra("dismissible", false)
42
+
43
+ // -------------------------------------------------------------------------
44
+ // Back button handling (modern API)
45
+ // -------------------------------------------------------------------------
46
+
47
+ if (!dismissible) {
48
+ onBackPressedDispatcher.addCallback(
49
+ this,
50
+ object : androidx.activity.OnBackPressedCallback(true) {
51
+ override fun handleOnBackPressed() {
52
+ // Intentionally disabled (secure-by-default)
53
+ }
54
+ },
55
+ )
56
+ }
57
+
58
+ // -------------------------------------------------------------------------
59
+ // Root layout (vertical)
60
+ // -------------------------------------------------------------------------
61
+
62
+ val root =
63
+ LinearLayout(this).apply {
64
+ orientation = LinearLayout.VERTICAL
65
+ layoutParams =
66
+ LinearLayout.LayoutParams(
67
+ LinearLayout.LayoutParams.MATCH_PARENT,
68
+ LinearLayout.LayoutParams.MATCH_PARENT,
69
+ )
70
+ }
71
+
72
+ // -------------------------------------------------------------------------
73
+ // Optional native toolbar (dismissible only)
74
+ // -------------------------------------------------------------------------
75
+
76
+ if (dismissible) {
77
+ val toolbar =
78
+ Toolbar(this).apply {
79
+ setNavigationIcon(android.R.drawable.ic_menu_close_clear_cancel)
80
+ setNavigationOnClickListener { finish() }
81
+ title = ""
82
+ }
83
+
84
+ root.addView(toolbar)
85
+ }
86
+
87
+ // -------------------------------------------------------------------------
88
+ // WebView setup
89
+ // -------------------------------------------------------------------------
90
+
91
+ val webView =
92
+ WebView(this).apply {
93
+ settings.javaScriptEnabled = true
94
+ settings.allowContentAccess = false
95
+ settings.domStorageEnabled = false
96
+ }
97
+
98
+ // Fill remaining space
99
+ val webViewParams =
100
+ LinearLayout.LayoutParams(
101
+ LinearLayout.LayoutParams.MATCH_PARENT,
102
+ 0,
103
+ 1f,
104
+ )
105
+ root.addView(webView, webViewParams)
106
+
107
+ setContentView(root)
108
+
109
+ // -------------------------------------------------------------------------
110
+ // URL handling
111
+ // -------------------------------------------------------------------------
112
+
113
+ // The plugin always passes the URL explicitly via Intent extras
114
+ val url = intent.getStringExtra("url") ?: return
115
+
116
+ // Remote URLs (http / https) are loaded directly
117
+ if (url.startsWith("http")) {
118
+ webView.loadUrl(url)
119
+ return
120
+ }
121
+
122
+ // -------------------------------------------------------------------------
123
+ // Local asset loading with query support
124
+ // -------------------------------------------------------------------------
125
+
126
+ // Example:
127
+ // url = "public/integrity-block.html?reason=integrity_failed"
128
+ val assetPath = url.substringBefore("?")
129
+ val query = url.substringAfter("?", "")
130
+
131
+ // Read the HTML asset manually
132
+ val html = readAsset(assetPath)
133
+
134
+ // Use a synthetic base URL so that:
135
+ // - window.location.search is populated
136
+ // - relative paths continue to work
137
+ webView.loadDataWithBaseURL(
138
+ "file:///android_asset/$assetPath?$query",
139
+ html,
140
+ "text/html",
141
+ "UTF-8",
142
+ null,
143
+ )
144
+
145
+ // -------------------------------------------------------------------------
146
+ // Back navigation blocking
147
+ // -------------------------------------------------------------------------
148
+
149
+ // Disable back gesture on Android 13+ (API 33+)
150
+ if (!dismissible && Build.VERSION.SDK_INT >= 33) {
151
+ onBackInvokedDispatcher.registerOnBackInvokedCallback(
152
+ android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT,
153
+ ) {
154
+ // Intentionally disabled
155
+ }
156
+ }
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Utilities
161
+ // ---------------------------------------------------------------------------
162
+
163
+ /**
164
+ * Reads a file from the android_asset directory.
165
+ *
166
+ * @param path Relative asset path (e.g. "public/integrity-block.html")
167
+ */
168
+ private fun readAsset(path: String): String {
169
+ val inputStream = assets.open(path)
170
+ val reader = BufferedReader(InputStreamReader(inputStream))
171
+ return reader.use { it.readText() }
172
+ }
173
+ }
@@ -0,0 +1,57 @@
1
+ package io.capkit.integrity.ui
2
+
3
+ import android.content.Context
4
+ import android.view.accessibility.AccessibilityManager
5
+ import io.capkit.integrity.IntegrityCheckOptions
6
+ import io.capkit.integrity.IntegritySignalBuilder
7
+ import io.capkit.integrity.IntegritySignalIds
8
+
9
+ /**
10
+ * Detects UI-level attacks such as screen overlays and
11
+ * suspicious accessibility services.
12
+ */
13
+ object IntegrityUISignals {
14
+ /**
15
+ * Checks for potential overlay attacks (Tapjacking).
16
+ * Combines accessibility service monitoring and window state heuristics.
17
+ */
18
+ fun checkOverlaySignals(
19
+ context: Context,
20
+ options: IntegrityCheckOptions,
21
+ ): Map<String, Any>? {
22
+ val signals = mutableMapOf<String, Any>()
23
+ val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
24
+
25
+ // Heuristic 1: Active Accessibility Services with Touch Exploration
26
+ // These services can read screen content and inject touches.
27
+ val isEnabled = am.isEnabled
28
+ val isTouchExplorationEnabled = am.isTouchExplorationEnabled
29
+
30
+ if (isEnabled && isTouchExplorationEnabled) {
31
+ return IntegritySignalBuilder.build(
32
+ id = IntegritySignalIds.ANDROID_OVERLAY_DETECTED,
33
+ category = "tamper",
34
+ confidence = "medium",
35
+ description = "Suspicious accessibility service state detected (potential overlay/UI spying)",
36
+ metadata =
37
+ mapOf(
38
+ "accessibility_enabled" to isEnabled,
39
+ "touch_exploration_enabled" to isTouchExplorationEnabled,
40
+ "source" to "AccessibilityManager",
41
+ ),
42
+ options = options,
43
+ )
44
+ }
45
+
46
+ // Heuristic 2: Detection via Window Focus (Passive Check)
47
+ // If the application is active but lacks focus, an overlay might be on top.
48
+ // This is a passive indicator used to increase the overall tamper score.
49
+ return null
50
+ }
51
+
52
+ /**
53
+ * Recommended native security practice:
54
+ * Developers should also set 'setFilterTouchesWhenObscured(true)'
55
+ * in their main View to prevent touches when an overlay is present.
56
+ */
57
+ }
@@ -0,0 +1,85 @@
1
+ package io.capkit.integrity.utils
2
+
3
+ import android.util.Log
4
+
5
+ /**
6
+ * Centralized logging utility for the Integrity plugin.
7
+ *
8
+ * This logging provides a single entry point for all native logs
9
+ * and supports runtime-controlled verbose logging.
10
+ *
11
+ * The goal is to avoid scattering `if (verbose)` checks across
12
+ * business logic and keep logging behavior consistent.
13
+ */
14
+ object IntegrityLogger {
15
+ /**
16
+ * Logcat tag used for all plugin logs.
17
+ * Helps filtering logs during debugging.
18
+ */
19
+ private const val TAG = "⚡️ Integrity"
20
+
21
+ /**
22
+ * Controls whether debug logs are printed.
23
+ *
24
+ * This flag should be set once during plugin initialization
25
+ * based on configuration values.
26
+ */
27
+ var verbose: Boolean = false
28
+
29
+ /**
30
+ * Prints a debug / verbose log message.
31
+ *
32
+ * This method should be used for development-time diagnostics
33
+ * and is automatically silenced when [verbose] is false.
34
+ *
35
+ * @param messages One or more message fragments to be concatenated.
36
+ */
37
+ fun debug(vararg messages: String) {
38
+ if (verbose) {
39
+ log(TAG, Log.DEBUG, *messages)
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Prints an error log message.
45
+ *
46
+ * Error logs are always printed regardless of [verbose] state.
47
+ *
48
+ * @param message Human-readable error description.
49
+ * @param e Optional exception for stack trace logging.
50
+ */
51
+ fun error(
52
+ message: String,
53
+ e: Throwable? = null,
54
+ ) {
55
+ val sb = StringBuilder(message)
56
+ if (e != null) {
57
+ sb.append(" | Error: ").append(e.message)
58
+ }
59
+ Log.e(TAG, sb.toString(), e)
60
+ }
61
+
62
+ /**
63
+ * Internal low-level log dispatcher.
64
+ *
65
+ * Joins message fragments and forwards them to Android's Log API
66
+ * using the specified priority.
67
+ */
68
+ fun log(
69
+ tag: String,
70
+ level: Int,
71
+ vararg messages: String,
72
+ ) {
73
+ val sb = StringBuilder()
74
+ for (msg in messages) {
75
+ sb.append(msg).append(" ")
76
+ }
77
+ when (level) {
78
+ Log.DEBUG -> Log.d(tag, sb.toString())
79
+ Log.INFO -> Log.i(tag, sb.toString())
80
+ Log.WARN -> Log.w(tag, sb.toString())
81
+ Log.ERROR -> Log.e(tag, sb.toString())
82
+ else -> Log.v(tag, sb.toString())
83
+ }
84
+ }
85
+ }