diff --git a/CHANGELOG.md b/CHANGELOG.md index 6717852..f454de4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0] - 2026-05-15 + +- Android SDK version: 18.3.0 +- iOS SDK version: 6.14.4 + +### Breaking + +- `SuspiciousAppInfo.reason` (String) renamed to `reasons` (Set) +- Value `"blacklist"` in `reasons` renamed to `"blocklist"` +- Removed `MalwareConfig` and `AndroidConfig.malwareConfig` +- `SuspiciousAppDetectionConfig.malwareScanScope` and `reasonMode` are now non-null with defaults `MalwareScanScope(ScopeType.SIDELOADED_ONLY)` and `ReasonMode.HIGHEST_CONFIDENCE` + +### Android + +#### Added + +- New API class `SuspiciousAppDetectionConfig` that can be used to configure malware detection +- New API for malware detection configuration in `TalsecConfig`, see `TalsecConfig.Builder#suspiciousAppDetection` + +#### Fixed + +- Fixed `VerifyError` caused by `JaCoCo` bytecode instrumentation +- Fixed a potential cause of crash in the multi-instance detector +- Fixed Java interoperability of `ScreenProtector` methods +- Fixed Kotlin classpath conflicts in SDK dependency resolution (Kotlin 2.0.0) + +#### Changed + +- Fine-tuned location spoofing detection +- Modified malware incident log structure for better aggregation + ## [1.1.0] - 2025-03-25 - Android SDK version: 18.0.4 diff --git a/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/App.kt b/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/App.kt index 242f891..5e39a64 100644 --- a/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/App.kt +++ b/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/App.kt @@ -12,7 +12,10 @@ import com.freeraspkmp.model.FreeRaspEvent import com.freeraspkmp.model.SuspiciousAppInfo import com.freeraspkmp.model.config.AndroidConfig import com.freeraspkmp.model.config.IOSConfig -import com.freeraspkmp.model.config.MalwareConfig +import com.freeraspkmp.model.config.ScanScope +import com.freeraspkmp.model.config.ReasonMode +import com.freeraspkmp.model.config.ScopeType +import com.freeraspkmp.model.config.SuspiciousAppDetectionConfig import com.freeraspkmp.model.config.freeraspConfig import com.jetbrains.example.model.initialChecks import com.jetbrains.example.model.toCheckId @@ -33,8 +36,25 @@ fun App() { androidConfig = AndroidConfig( packageName = "com.jetbrains.example", certificateHashes = listOf("K/iFV7+CypnATFWcrUVM6aUIB5gnU2xwzRJOiKJJqPw="), - malwareConfig = MalwareConfig( - blacklistedPackageNames = listOf("com.google.android.youtube") + suspiciousAppDetectionConfig = SuspiciousAppDetectionConfig( + packageNames = listOf("com.google.android.youtube"), + hashes = listOf("FgvSehLMM91E7lX/Zqp3u4jMmd0A7hH/Iqozu0TMVd0u"), + requestedPermissions = listOf( + listOf( + "android.permission.INTERNET", + "android.permission.ACCESS_COARSE_LOCATION", + ), + listOf("android.permission.BLUETOOTH"), + listOf("android.permission.BATTERY_STATS"), + ), + grantedPermissions = listOf( + listOf("android.permission.ACCESS_FINE_LOCATION"), + ), + scanScope = ScanScope( + scopeType = ScopeType.SIDELOADED_AND_SYSTEM_EXCLUDE_OEM, + trustedInstallSources = listOf("com.apkpure.aegon"), + ), + reasonMode = ReasonMode.HIGHEST_CONFIDENCE, ) ), iosConfig = IOSConfig( diff --git a/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/ui/MalwareBottomSheet.kt b/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/ui/MalwareBottomSheet.kt index cce38fb..112081e 100644 --- a/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/ui/MalwareBottomSheet.kt +++ b/example/composeApp/src/commonMain/kotlin/com/jetbrains/example/ui/MalwareBottomSheet.kt @@ -139,7 +139,7 @@ private fun MalwareAppItem(app: SuspiciousAppInfo) { color = MaterialTheme.colorScheme.onSurfaceVariant, ) Text( - text = "Reason: ${app.reason}", + text = "Reasons: ${app.reasons.joinToString(", ")}", style = MaterialTheme.typography.bodySmall, color = ThreatRed, ) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 0e34177..911be4d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -19,7 +19,7 @@ fun getVariable(name: String): String { group = "com.aheaditec.talsec.security" -version = "1.1.0" +version = "2.0.0" kotlin { targets.all { compilations.all { @@ -77,7 +77,7 @@ kotlin { languageSettings.optIn("kotlin.ExperimentalMultiplatform") dependencies{ - implementation("com.aheaditec.talsec.security:TalsecSecurity-Community-KMP:18.0.4") + implementation("com.aheaditec.talsec.security:TalsecSecurity-Community-KMP:18.3.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0") implementation("androidx.startup:startup-runtime:1.2.0") implementation("androidx.annotation:annotation:1.9.1") diff --git a/library/src/androidMain/kotlin/utils/config_mapper.android.kt b/library/src/androidMain/kotlin/utils/config_mapper.android.kt index f2e81b3..028939a 100644 --- a/library/src/androidMain/kotlin/utils/config_mapper.android.kt +++ b/library/src/androidMain/kotlin/utils/config_mapper.android.kt @@ -1,8 +1,16 @@ package com.freeraspkmp.android.utils import com.aheaditec.talsec_security.security.api.TalsecConfig +import com.freeraspkmp.model.config.ScanScope +import com.freeraspkmp.model.config.ReasonMode +import com.freeraspkmp.model.config.ScopeType +import com.freeraspkmp.model.config.SuspiciousAppDetectionConfig import com.freeraspkmp.model.config.freeraspConfig import com.freeraspkmp.model.exception.FreeraspKMPException +import com.aheaditec.talsec_security.security.api.SuspiciousAppDetectionConfig as NativeSuspiciousAppDetectionConfig +import com.aheaditec.talsec_security.security.api.MalwareScanScope as NativeMalwareScanScope +import com.aheaditec.talsec_security.security.api.ScopeType as NativeScopeType +import com.aheaditec.talsec_security.security.api.ReasonMode as NativeReasonMode fun freeraspConfig.toNativeConfig(): TalsecConfig { val androidConfig = this.androidConfig ?: throw FreeraspKMPException("AndroidConfig is required on the Android platform but was null.") @@ -21,29 +29,41 @@ fun freeraspConfig.toNativeConfig(): TalsecConfig { supportedAlternativeStores(it.toTypedArray()) } - androidConfig.malwareConfig?.let { malware -> - if(malware.blacklistedPackageNames.isNotEmpty()) - { - blacklistedPackageNames(malware.blacklistedPackageNames.toTypedArray()) - } - if(malware.blacklistedHashes.isNotEmpty()) - { - blacklistedHashes(malware.blacklistedHashes.toTypedArray()) - } - if (malware.suspiciousPermissions.isNotEmpty()) - { - val nativePermissions = malware.suspiciousPermissions - .map { innerList -> innerList.toTypedArray() } - .toTypedArray() - - suspiciousPermissions(nativePermissions) - } - if(malware.whitelistedInstallationSources.isNotEmpty()) - { - whitelistedInstallationSources(malware.whitelistedInstallationSources.toTypedArray()) - } + androidConfig.suspiciousAppDetectionConfig?.let { + suspiciousAppDetection(it.toNative()) } } return builder.build() -} \ No newline at end of file +} + +internal fun SuspiciousAppDetectionConfig.toNative(): NativeSuspiciousAppDetectionConfig = + NativeSuspiciousAppDetectionConfig( + packageNames?.toSet(), + hashes?.toSet(), + requestedPermissions?.map { it.toSet() }?.toSet(), + grantedPermissions?.map { it.toSet() }?.toSet(), + scanScope.toNative(), + reasonMode.toNative() + ) + +internal fun ScanScope.toNative(): NativeMalwareScanScope = + NativeMalwareScanScope( + scopeType.toNative(), + trustedInstallSources + ) + +internal fun ScopeType.toNative(): NativeScopeType = + when (this) { + ScopeType.SIDELOADED_ONLY -> NativeScopeType.SIDELOADED_ONLY + ScopeType.SIDELOADED_AND_SYSTEM_EXCLUDE_OEM -> NativeScopeType.SIDELOADED_AND_SYSTEM_EXCLUDE_OEM + ScopeType.SIDELOADED_AND_OEM -> NativeScopeType.SIDELOADED_AND_OEM + ScopeType.SIDELOADED_AND_SYSTEM_AND_OEM -> NativeScopeType.SIDELOADED_AND_SYSTEM_AND_OEM + ScopeType.ALL -> NativeScopeType.ALL + } + +internal fun ReasonMode.toNative(): NativeReasonMode = + when (this) { + ReasonMode.ALL -> NativeReasonMode.ALL + ReasonMode.HIGHEST_CONFIDENCE -> NativeReasonMode.HIGHEST_CONFIDENCE + } diff --git a/library/src/androidMain/kotlin/utils/malware_data_processor.android.kt b/library/src/androidMain/kotlin/utils/malware_data_processor.android.kt index ac12779..eb8f7aa 100644 --- a/library/src/androidMain/kotlin/utils/malware_data_processor.android.kt +++ b/library/src/androidMain/kotlin/utils/malware_data_processor.android.kt @@ -17,7 +17,7 @@ internal fun processMalwareData( return nativeList.mapNotNull { nativeInfo -> try { val pInfo = nativeInfo.packageInfo - val reason = nativeInfo.reason + val reasons = nativeInfo.reasons val permissions = nativeInfo.permissions val packageName = pInfo.packageName @@ -36,7 +36,7 @@ internal fun processMalwareData( SuspiciousAppInfo( packageInfo = commonPackageInfo, - reason = reason, + reasons = reasons, permissions = permissions ?: emptySet() ) } catch (e: Exception) { diff --git a/library/src/commonMain/kotlin/model/config/android_config.kt b/library/src/commonMain/kotlin/model/config/android_config.kt index 2d4eb6b..b081bb1 100644 --- a/library/src/commonMain/kotlin/model/config/android_config.kt +++ b/library/src/commonMain/kotlin/model/config/android_config.kt @@ -6,11 +6,11 @@ package com.freeraspkmp.model.config * @param packageName The expected package name of the app. * @param certificateHashes A list of expected certificate hashes in base64. * @param supportedAlternativeStores A list of package names for supported alternative stores. - * @param malwareConfig Configuration for malware detection. + * @param suspiciousAppDetectionConfig Configuration for malware detection. */ data class AndroidConfig( val packageName: String, val certificateHashes: List, val supportedAlternativeStores: List = emptyList(), - val malwareConfig: MalwareConfig? = null + val suspiciousAppDetectionConfig: SuspiciousAppDetectionConfig? = null ) diff --git a/library/src/commonMain/kotlin/model/config/malware_config.kt b/library/src/commonMain/kotlin/model/config/malware_config.kt deleted file mode 100644 index 30a12d5..0000000 --- a/library/src/commonMain/kotlin/model/config/malware_config.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.freeraspkmp.model.config - -/** - * Configuration for malware detection. - * - * @param blacklistedPackageNames A list of blacklisted package names. - * @param blacklistedHashes A list of blacklisted app certificate hashes. - * @param suspiciousPermissions A list of suspicious permission groups. - * @param whitelistedInstallationSources A list of whitelisted installation sources. - */ -data class MalwareConfig ( - val blacklistedPackageNames: List = emptyList(), - val blacklistedHashes: List = emptyList(), - val suspiciousPermissions: List> = emptyList(), - val whitelistedInstallationSources: List = emptyList() -) \ No newline at end of file diff --git a/library/src/commonMain/kotlin/model/config/suspicious_app_detection_config.kt b/library/src/commonMain/kotlin/model/config/suspicious_app_detection_config.kt new file mode 100644 index 0000000..01c9845 --- /dev/null +++ b/library/src/commonMain/kotlin/model/config/suspicious_app_detection_config.kt @@ -0,0 +1,49 @@ +package com.freeraspkmp.model.config + +/** + * Scope of installed apps included in the malware scan. + */ +enum class ScopeType { + SIDELOADED_ONLY, + SIDELOADED_AND_SYSTEM_EXCLUDE_OEM, + SIDELOADED_AND_OEM, + SIDELOADED_AND_SYSTEM_AND_OEM, + ALL +} + +/** + * Controls how detection reasons are reported on a suspicious app. + */ +enum class ReasonMode { ALL, HIGHEST_CONFIDENCE } + +/** + * Defines which installed apps should be scanned for malware. + * + * @param scopeType The set of apps to include in the scan. + * @param trustedInstallSources Installation sources whose apps should be excluded from the scan. + */ +data class ScanScope( + val scopeType: ScopeType, + val trustedInstallSources: List? = null +) + +/** + * Configuration for malware detection. + * + * @param packageNames Package names of known malicious apps. + * @param hashes Certificate hashes of known malicious apps. + * @param requestedPermissions Groups of permissions an app must request to be flagged as suspicious. + * @param grantedPermissions Groups of permissions an app must be granted to be flagged as suspicious. + * @param scanScope Defines which apps are scanned. + * Defaults to [ScanScope] with [ScopeType.SIDELOADED_ONLY]. + * @param reasonMode Controls how detection reasons are reported. + * Defaults to [ReasonMode.HIGHEST_CONFIDENCE]. + */ +data class SuspiciousAppDetectionConfig( + val packageNames: List? = null, + val hashes: List? = null, + val requestedPermissions: List>? = null, + val grantedPermissions: List>? = null, + val scanScope: ScanScope = ScanScope(ScopeType.SIDELOADED_ONLY), + val reasonMode: ReasonMode = ReasonMode.HIGHEST_CONFIDENCE +) diff --git a/library/src/commonMain/kotlin/model/suspicious_app_info.kt b/library/src/commonMain/kotlin/model/suspicious_app_info.kt index 426f58c..8ac458a 100644 --- a/library/src/commonMain/kotlin/model/suspicious_app_info.kt +++ b/library/src/commonMain/kotlin/model/suspicious_app_info.kt @@ -4,12 +4,12 @@ package com.freeraspkmp.model * Contains information about a suspicious app. * * @param packageInfo Information about the suspicious package. - * @param reason The reason why the app is considered suspicious. - * @param permissions A set of suspicious permissions held by the app. Populated when reason is `suspiciousPermission`. + * @param reasons The reasons why the app is considered suspicious. + * @param permissions A set of suspicious permissions held by the app. Populated when reasons contain `suspiciousPermission`. */ data class SuspiciousAppInfo( val packageInfo: PackageInfo, - val reason: String, + val reasons: Set, val permissions: Set = emptySet() )