@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,104 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.2.20'
|
|
3
|
+
repositories {
|
|
4
|
+
google()
|
|
5
|
+
mavenCentral()
|
|
6
|
+
}
|
|
7
|
+
dependencies {
|
|
8
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
9
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
plugins {
|
|
14
|
+
id "org.jlleitschuh.gradle.ktlint" version "12.1.1" apply false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
ext {
|
|
18
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
19
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
20
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
21
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
22
|
+
androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.17.0'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
apply plugin: 'com.android.library'
|
|
26
|
+
apply plugin: 'kotlin-android'
|
|
27
|
+
apply plugin: 'kotlin-parcelize'
|
|
28
|
+
apply plugin: 'org.jlleitschuh.gradle.ktlint'
|
|
29
|
+
|
|
30
|
+
import groovy.json.JsonSlurper
|
|
31
|
+
|
|
32
|
+
def getPluginVersion() {
|
|
33
|
+
try {
|
|
34
|
+
def packageJsonFile = file('../package.json')
|
|
35
|
+
if (packageJsonFile.exists()) {
|
|
36
|
+
def packageJson = new JsonSlurper().parseText(packageJsonFile.text)
|
|
37
|
+
return packageJson.version
|
|
38
|
+
}
|
|
39
|
+
} catch (Exception e) {
|
|
40
|
+
// Ignore errors and fallback
|
|
41
|
+
logger.info("Exception", e)
|
|
42
|
+
}
|
|
43
|
+
return "8.0.0"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
def pluginVersion = getPluginVersion()
|
|
47
|
+
|
|
48
|
+
android {
|
|
49
|
+
namespace = "io.capkit.integrity"
|
|
50
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion as Integer : 36
|
|
51
|
+
|
|
52
|
+
// AGP 8.0+ disables BuildConfig by default for libraries.
|
|
53
|
+
// We need to enable it to inject the plugin version.
|
|
54
|
+
buildFeatures {
|
|
55
|
+
buildConfig = true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
defaultConfig {
|
|
59
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion as Integer : 24
|
|
60
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion as Integer : 36
|
|
61
|
+
versionCode 1
|
|
62
|
+
|
|
63
|
+
// Dynamic versioning (feature enabled)
|
|
64
|
+
versionName = pluginVersion
|
|
65
|
+
|
|
66
|
+
// Injects the version into the BuildConfig class ONLY if feature is enabled
|
|
67
|
+
buildConfigField "String", "PLUGIN_VERSION", "\"${pluginVersion}\""
|
|
68
|
+
|
|
69
|
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
70
|
+
}
|
|
71
|
+
buildTypes {
|
|
72
|
+
release {
|
|
73
|
+
minifyEnabled = false
|
|
74
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
lint {
|
|
78
|
+
abortOnError = false
|
|
79
|
+
}
|
|
80
|
+
compileOptions {
|
|
81
|
+
sourceCompatibility = JavaVersion.VERSION_21
|
|
82
|
+
targetCompatibility = JavaVersion.VERSION_21
|
|
83
|
+
}
|
|
84
|
+
kotlinOptions {
|
|
85
|
+
jvmTarget = JavaVersion.VERSION_21
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
repositories {
|
|
90
|
+
google()
|
|
91
|
+
mavenCentral()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
dependencies {
|
|
95
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
96
|
+
implementation project(':capacitor-android')
|
|
97
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
98
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
99
|
+
implementation "androidx.core:core-ktx:$androidxCoreKTXVersion"
|
|
100
|
+
|
|
101
|
+
testImplementation "junit:junit:$junitVersion"
|
|
102
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
103
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
104
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
3
|
+
|
|
4
|
+
<queries>
|
|
5
|
+
<package android:name="com.noshufou.android.su" />
|
|
6
|
+
<package android:name="com.thirdparty.superuser" />
|
|
7
|
+
<package android:name="eu.chainfire.supersu" />
|
|
8
|
+
<package android:name="com.koushikdutta.superuser" />
|
|
9
|
+
<package android:name="com.zachareew.systemuituner" />
|
|
10
|
+
<package android:name="com.topjohnwu.magisk" />
|
|
11
|
+
<package android:name="com.alephzain.framaroot" />
|
|
12
|
+
<package android:name="org.adaway" />
|
|
13
|
+
</queries>
|
|
14
|
+
|
|
15
|
+
<application>
|
|
16
|
+
<activity
|
|
17
|
+
android:name=".ui.IntegrityBlockActivity"
|
|
18
|
+
android:exported="false"
|
|
19
|
+
android:theme="@style/IntegrityBlockTheme" />
|
|
20
|
+
</application>
|
|
21
|
+
</manifest>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package io.capkit.integrity
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native representation of `Integrity.check()` options.
|
|
5
|
+
*
|
|
6
|
+
* This model mirrors the JavaScript options object and is used
|
|
7
|
+
* to control how integrity checks are executed.
|
|
8
|
+
*
|
|
9
|
+
* Design principles:
|
|
10
|
+
* - Independent from the Plugin (Bridge) layer
|
|
11
|
+
* - Safe to use inside the native Implementation layer
|
|
12
|
+
* - Derived exclusively from JS input
|
|
13
|
+
* - Does NOT alter the public JS API shape
|
|
14
|
+
*
|
|
15
|
+
* Notes:
|
|
16
|
+
* - Default values are applied in the Plugin layer
|
|
17
|
+
* - The Implementation layer assumes normalized values
|
|
18
|
+
*/
|
|
19
|
+
data class IntegrityCheckOptions(
|
|
20
|
+
/**
|
|
21
|
+
* Desired strictness level for integrity checks.
|
|
22
|
+
*
|
|
23
|
+
* Supported values:
|
|
24
|
+
* - "basic": minimal checks (root, emulator)
|
|
25
|
+
* - "standard": adds debug and instrumentation checks
|
|
26
|
+
* - "strict": enables all available heuristics
|
|
27
|
+
*/
|
|
28
|
+
val level: String,
|
|
29
|
+
/**
|
|
30
|
+
* Whether debug-only information should be included
|
|
31
|
+
* in returned integrity signals.
|
|
32
|
+
*
|
|
33
|
+
* When true, signals MAY contain a human-readable
|
|
34
|
+
* `description` field for diagnostic purposes.
|
|
35
|
+
*/
|
|
36
|
+
val includeDebugInfo: Boolean,
|
|
37
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
package io.capkit.integrity
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.getcapacitor.Plugin
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Plugin configuration container.
|
|
8
|
+
*
|
|
9
|
+
* This class is responsible for reading and exposing
|
|
10
|
+
* static configuration values defined under the
|
|
11
|
+
* `Integrity` key in capacitor.config.ts.
|
|
12
|
+
*
|
|
13
|
+
* Configuration rules:
|
|
14
|
+
* - Read once during plugin initialization
|
|
15
|
+
* - Treated as immutable runtime input
|
|
16
|
+
* - Accessible only from native code
|
|
17
|
+
*/
|
|
18
|
+
class IntegrityConfig(plugin: Plugin) {
|
|
19
|
+
/**
|
|
20
|
+
* Android application context.
|
|
21
|
+
* Exposed for native components that may require it.
|
|
22
|
+
*/
|
|
23
|
+
val context: Context = plugin.context
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Enables verbose native logging.
|
|
27
|
+
*
|
|
28
|
+
* When enabled, additional debug information
|
|
29
|
+
* is printed to Logcat.
|
|
30
|
+
*
|
|
31
|
+
* Default: false
|
|
32
|
+
*/
|
|
33
|
+
val verboseLogging: Boolean
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* Default: false
|
|
38
|
+
*/
|
|
39
|
+
val blockPageEnabled: Boolean
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
44
|
+
val blockPageUrl: String?
|
|
45
|
+
|
|
46
|
+
init {
|
|
47
|
+
val config = plugin.getConfig()
|
|
48
|
+
|
|
49
|
+
// Verbose logging flag
|
|
50
|
+
verboseLogging =
|
|
51
|
+
config.getBoolean("verboseLogging", false)
|
|
52
|
+
|
|
53
|
+
val blockPage = config.getObject("blockPage")
|
|
54
|
+
|
|
55
|
+
blockPageEnabled = blockPage?.getBoolean("enabled") ?: false
|
|
56
|
+
|
|
57
|
+
blockPageUrl = blockPage?.getString("url")
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package io.capkit.integrity
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native error model for the Integrity plugin (Android).
|
|
5
|
+
*
|
|
6
|
+
* Architectural rules:
|
|
7
|
+
* - Must NOT reference Capacitor APIs
|
|
8
|
+
* - Must NOT reference JavaScript
|
|
9
|
+
* - Must be throwable from the Impl layer
|
|
10
|
+
* - Mapping to JS-facing error codes happens ONLY in the Plugin layer
|
|
11
|
+
*/
|
|
12
|
+
sealed class IntegrityError(
|
|
13
|
+
message: String,
|
|
14
|
+
) : Throwable(message) {
|
|
15
|
+
/**
|
|
16
|
+
* Feature or capability is not available
|
|
17
|
+
* due to device or configuration limitations.
|
|
18
|
+
*/
|
|
19
|
+
class Unavailable(message: String) :
|
|
20
|
+
IntegrityError(message)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Required permission was denied or not granted.
|
|
24
|
+
*/
|
|
25
|
+
class PermissionDenied(message: String) :
|
|
26
|
+
IntegrityError(message)
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Plugin failed to initialize or perform
|
|
30
|
+
* a required operation.
|
|
31
|
+
*/
|
|
32
|
+
class InitFailed(message: String) :
|
|
33
|
+
IntegrityError(message)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Invalid or unsupported input was provided.
|
|
37
|
+
*/
|
|
38
|
+
class UnknownType(message: String) :
|
|
39
|
+
IntegrityError(message)
|
|
40
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
package io.capkit.integrity
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import io.capkit.integrity.emulator.IntegrityEmulatorChecks
|
|
5
|
+
import io.capkit.integrity.filesystem.IntegrityFilesystemChecks
|
|
6
|
+
import io.capkit.integrity.hook.IntegrityHookChecks
|
|
7
|
+
import io.capkit.integrity.remote.IntegrityRemoteAttestor
|
|
8
|
+
import io.capkit.integrity.root.IntegrityRootDetector
|
|
9
|
+
import io.capkit.integrity.runtime.IntegrityRuntimeChecks
|
|
10
|
+
import io.capkit.integrity.ui.IntegrityUISignals
|
|
11
|
+
import io.capkit.integrity.utils.IntegrityLogger
|
|
12
|
+
import java.util.Collections
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Native Android implementation for the Integrity plugin.
|
|
16
|
+
*
|
|
17
|
+
* CONTRACT:
|
|
18
|
+
* - This class MUST NOT reference:
|
|
19
|
+
* - PluginCall
|
|
20
|
+
* - Capacitor APIs
|
|
21
|
+
* - Activities or UI components
|
|
22
|
+
*
|
|
23
|
+
* Responsibilities:
|
|
24
|
+
* - Perform platform-specific integrity checks
|
|
25
|
+
* - Interact with Android system APIs
|
|
26
|
+
* - Produce platform-agnostic integrity signals
|
|
27
|
+
*
|
|
28
|
+
* Error handling:
|
|
29
|
+
* - MUST throw typed IntegrityError on unrecoverable failures
|
|
30
|
+
* - MUST NOT swallow fatal initialization errors
|
|
31
|
+
*/
|
|
32
|
+
class IntegrityImpl(
|
|
33
|
+
private val context: Context,
|
|
34
|
+
) {
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Signal lifecycle & execution model
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Signal production in this implementation follows a two-phase model:
|
|
41
|
+
*
|
|
42
|
+
* 1. Early boot phase (optional, best-effort)
|
|
43
|
+
* - Signals may be captured before the Capacitor bridge is initialized.
|
|
44
|
+
* - Early signals are collected via the static `onApplicationCreate` entry point.
|
|
45
|
+
* - Captured signals are stored in a volatile in-memory buffer (`bootSignals`).
|
|
46
|
+
* - Early boot detection is opportunistic and NOT guaranteed.
|
|
47
|
+
*
|
|
48
|
+
* 2. Runtime phase (authoritative)
|
|
49
|
+
* - Signals are produced when `check(...)` is invoked from JavaScript.
|
|
50
|
+
* - Any previously captured boot signals are merged into the first report.
|
|
51
|
+
* - Subsequent checks operate exclusively on runtime detection.
|
|
52
|
+
*
|
|
53
|
+
* IMPORTANT SEMANTICS:
|
|
54
|
+
* - Boot signals are best-effort and may be absent depending on
|
|
55
|
+
* process lifecycle, OS behavior, or warm starts.
|
|
56
|
+
* - Boot signals do NOT provide stronger guarantees than runtime signals.
|
|
57
|
+
* - The absence of boot signals does NOT indicate a clean environment.
|
|
58
|
+
*
|
|
59
|
+
* This design improves detection timing, not detection coverage.
|
|
60
|
+
*/
|
|
61
|
+
companion object {
|
|
62
|
+
/**
|
|
63
|
+
* Volatile buffer for signals captured during early boot.
|
|
64
|
+
*/
|
|
65
|
+
private val bootSignals = Collections.synchronizedList(mutableListOf<Map<String, Any>>())
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Native entry point for MainActivity.onCreate.
|
|
69
|
+
* Captures security signals before the Capacitor bridge is initialized.
|
|
70
|
+
*/
|
|
71
|
+
@JvmStatic
|
|
72
|
+
fun onApplicationCreate(context: Context) {
|
|
73
|
+
// Capture basic root signals immediately at boot using the dedicated detector
|
|
74
|
+
val signals =
|
|
75
|
+
IntegrityRootDetector.checkRootSignals(
|
|
76
|
+
IntegrityCheckOptions(
|
|
77
|
+
level = "basic",
|
|
78
|
+
includeDebugInfo = false,
|
|
79
|
+
),
|
|
80
|
+
allowCache = false,
|
|
81
|
+
)
|
|
82
|
+
bootSignals.addAll(signals)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Negative cache for expensive integrity checks.
|
|
87
|
+
// Caches only "no-signal" results for a short time window.
|
|
88
|
+
private data class NegativeCacheEntry(
|
|
89
|
+
val timestampMs: Long,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
private val negativeCache = mutableMapOf<String, NegativeCacheEntry>()
|
|
93
|
+
|
|
94
|
+
private val negativeCacheTtlMs = 30_000L
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Remote Attestation
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Stub for Google Play Integrity integration.
|
|
102
|
+
* To be implemented in a future evolution step.
|
|
103
|
+
*/
|
|
104
|
+
fun getPlayIntegritySignal(options: IntegrityCheckOptions): Map<String, Any>? {
|
|
105
|
+
return IntegrityRemoteAttestor.getPlayIntegritySignal(context, options)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Configuration
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Cached immutable plugin configuration.
|
|
114
|
+
*/
|
|
115
|
+
private lateinit var config: IntegrityConfig
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Applies static plugin configuration.
|
|
119
|
+
*/
|
|
120
|
+
fun updateConfig(newConfig: IntegrityConfig) {
|
|
121
|
+
this.config = newConfig
|
|
122
|
+
IntegrityLogger.verbose = newConfig.verboseLogging
|
|
123
|
+
|
|
124
|
+
IntegrityLogger.debug(
|
|
125
|
+
"Integrity configuration applied. Verbose logging:",
|
|
126
|
+
newConfig.verboseLogging.toString(),
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Options orchestrator
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Executes the requested integrity checks and aggregates signals.
|
|
136
|
+
*/
|
|
137
|
+
fun performCheck(options: IntegrityCheckOptions): Map<String, Any> {
|
|
138
|
+
// Apply negative cache only for standard / strict levels.
|
|
139
|
+
// Cached results represent a recent "no-signal" execution.
|
|
140
|
+
if (options.level != "basic" && isNegativeCacheValid(options.level)) {
|
|
141
|
+
IntegrityLogger.debug(
|
|
142
|
+
"Negative cache hit for integrity check:",
|
|
143
|
+
options.level,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return IntegrityReportBuilder.buildReport(
|
|
147
|
+
emptyList(),
|
|
148
|
+
isEmulator = false,
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
val signals = mutableListOf<Map<String, Any>>()
|
|
153
|
+
|
|
154
|
+
// --- BOOT SIGNALS ----------------------------------------------------
|
|
155
|
+
// Merge signals captured during early boot and clear the buffer.
|
|
156
|
+
synchronized(bootSignals) {
|
|
157
|
+
signals.addAll(bootSignals)
|
|
158
|
+
bootSignals.clear()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// --- BASIC -----------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
// Root filesystem & build-tag detection (cached, runtime)
|
|
164
|
+
signals.addAll(
|
|
165
|
+
IntegrityRootDetector.checkRootSignals(
|
|
166
|
+
options = options,
|
|
167
|
+
allowCache = true,
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
// Known root management packages (runtime-only)
|
|
172
|
+
signals.addAll(
|
|
173
|
+
IntegrityRootDetector.checkRootPackages(
|
|
174
|
+
context = context,
|
|
175
|
+
options = options,
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
// Sandbox escape heuristics (non-deterministic, filesystem-based)
|
|
180
|
+
IntegrityFilesystemChecks
|
|
181
|
+
.checkSandboxEscape(options)
|
|
182
|
+
?.let { signals.add(it) }
|
|
183
|
+
|
|
184
|
+
val isEmulator = IntegrityEmulatorChecks.isEmulator()
|
|
185
|
+
if (isEmulator) {
|
|
186
|
+
signals.add(
|
|
187
|
+
IntegritySignalBuilder.build(
|
|
188
|
+
id = "android_emulator",
|
|
189
|
+
category = "emulator",
|
|
190
|
+
confidence = "high",
|
|
191
|
+
description = "Execution environment matches known emulator characteristics",
|
|
192
|
+
// Added metadata to identify common emulator properties
|
|
193
|
+
metadata =
|
|
194
|
+
mapOf(
|
|
195
|
+
"model" to android.os.Build.MODEL,
|
|
196
|
+
"manufacturer" to android.os.Build.MANUFACTURER,
|
|
197
|
+
"hardware" to android.os.Build.HARDWARE,
|
|
198
|
+
),
|
|
199
|
+
options = options,
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// --- STANDARD & STRICT -----------------------------------------------
|
|
205
|
+
|
|
206
|
+
if (options.level != "basic") {
|
|
207
|
+
// checkDebug returns a list of signals
|
|
208
|
+
signals.addAll(IntegrityRuntimeChecks.checkDebugSignals(context, options))
|
|
209
|
+
|
|
210
|
+
// UI and Overlay detection (RASP)
|
|
211
|
+
IntegrityUISignals
|
|
212
|
+
.checkOverlaySignals(context, options)
|
|
213
|
+
?.let { signals.add(it) }
|
|
214
|
+
|
|
215
|
+
// --- HOOKING DETECTION --------------------------------------------
|
|
216
|
+
|
|
217
|
+
// Frida detection via memory maps
|
|
218
|
+
val memHook = IntegrityHookChecks.checkFridaMemory()
|
|
219
|
+
|
|
220
|
+
if (memHook) {
|
|
221
|
+
signals.add(
|
|
222
|
+
IntegritySignalBuilder.build(
|
|
223
|
+
id = "android_frida_memory",
|
|
224
|
+
category = "hook",
|
|
225
|
+
confidence = "high",
|
|
226
|
+
description = "Process memory contains known instrumentation artifacts",
|
|
227
|
+
options = options,
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Frida detection via known ports
|
|
233
|
+
val portHook = IntegrityHookChecks.checkFridaPorts()
|
|
234
|
+
if (portHook) {
|
|
235
|
+
signals.add(
|
|
236
|
+
IntegritySignalBuilder.build(
|
|
237
|
+
id = "android_frida_port",
|
|
238
|
+
category = "hook",
|
|
239
|
+
confidence = "medium",
|
|
240
|
+
description = "Known instrumentation ports are reachable on localhost",
|
|
241
|
+
options = options,
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Signal correlation logic
|
|
247
|
+
if (memHook && portHook) {
|
|
248
|
+
signals.add(
|
|
249
|
+
IntegritySignalBuilder.build(
|
|
250
|
+
id = "android_frida_correlation_confirmed",
|
|
251
|
+
category = "hook",
|
|
252
|
+
confidence = "high",
|
|
253
|
+
description = "Multiple instrumentation indicators detected simultaneously",
|
|
254
|
+
metadata = mapOf("source" to "memory+port"),
|
|
255
|
+
options = options,
|
|
256
|
+
),
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// --- STRICT --------------------------------------------------------------
|
|
262
|
+
|
|
263
|
+
if (options.level == "strict") {
|
|
264
|
+
// Google Play Integrity (future remote attestation)
|
|
265
|
+
IntegrityRemoteAttestor
|
|
266
|
+
.getPlayIntegritySignal(context, options)
|
|
267
|
+
?.let { signals.add(it) }
|
|
268
|
+
|
|
269
|
+
// Verify application signature integrity
|
|
270
|
+
if (!IntegrityRuntimeChecks.checkAppSignature(context)) {
|
|
271
|
+
signals.add(
|
|
272
|
+
IntegritySignalBuilder.build(
|
|
273
|
+
id = "android_signature_invalid",
|
|
274
|
+
category = "tamper",
|
|
275
|
+
confidence = "high",
|
|
276
|
+
description = "Application signing information does not match expected format",
|
|
277
|
+
// Added metadata to specify the source of the signature check failure
|
|
278
|
+
metadata = mapOf("package" to context.packageName),
|
|
279
|
+
options = options,
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Update negative cache only when no integrity signals are detected.
|
|
286
|
+
// Any detected signal invalidates the cached clean state.
|
|
287
|
+
if (options.level != "basic") {
|
|
288
|
+
if (signals.isEmpty()) {
|
|
289
|
+
updateNegativeCache(options.level)
|
|
290
|
+
} else {
|
|
291
|
+
clearNegativeCache(options.level)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Final report assembly using the dedicated builder
|
|
296
|
+
return IntegrityReportBuilder.buildReport(
|
|
297
|
+
signals,
|
|
298
|
+
isEmulator,
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private fun cacheKey(level: String): String {
|
|
303
|
+
return "android:$level"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private fun isNegativeCacheValid(level: String): Boolean {
|
|
307
|
+
val entry = negativeCache[cacheKey(level)] ?: return false
|
|
308
|
+
return System.currentTimeMillis() - entry.timestampMs <= negativeCacheTtlMs
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private fun updateNegativeCache(level: String) {
|
|
312
|
+
negativeCache[cacheKey(level)] =
|
|
313
|
+
NegativeCacheEntry(System.currentTimeMillis())
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private fun clearNegativeCache(level: String) {
|
|
317
|
+
negativeCache.remove(cacheKey(level))
|
|
318
|
+
}
|
|
319
|
+
}
|