Skip to content

Commit 02a4b15

Browse files
authored
Merge pull request #137 from talsec/release/6.8.0
freeRASP 6.8.0
2 parents 6c0d660 + 5d4f97c commit 02a4b15

File tree

67 files changed

+1702
-755
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1702
-755
lines changed

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [6.8.0] - 2024-10-01
9+
- Android SDK version: 12.0.0
10+
- iOS SDK version: 6.6.3
11+
12+
### Flutter
13+
14+
#### Added
15+
- New feature: Malware detection as a new callback for enhanced app security
16+
17+
### Android
18+
19+
#### Changed
20+
- Internal refactoring of Malware detection feature
21+
22+
#### Fixed
23+
- Refactoring Magisk checks in the root detection
24+
- Resolving IllegalArgumentException caused by unregistering not registered receiver in TalsecMonitoringReceiver
25+
26+
### iOS
27+
28+
#### Added
29+
30+
- Enhanced security with **[Serotonin Jailbreak](https://github.com/SerotoninApp/Serotonin) Detection** to identify compromised devices.
31+
32+
#### Changed
33+
34+
- Updated SDK code signing; it will now be signed with:
35+
- Team ID: PBDDS45LQS
36+
- Team Name: Lynx SFT s.r.o.
37+
838
## [6.7.3] - 2024-10-28
939
- Android SDK version: 11.1.3
1040
- iOS SDK version: 6.6.1

analysis_options.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
include: package:very_good_analysis/analysis_options.3.1.0.yaml
1+
include: package:very_good_analysis/analysis_options.yaml
2+
3+
analyzer:
4+
exclude:
5+
- '**/*.g.dart'

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version '1.0-SNAPSHOT'
33

44
buildscript {
55
ext.kotlin_version = '1.7.20'
6-
ext.talsec_version = '11.1.3'
6+
ext.talsec_version = '12.0.0'
77
repositories {
88
google()
99
mavenCentral()

android/src/main/kotlin/com/aheaditec/freerasp/Extensions.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
package com.aheaditec.freerasp
22

3+
import android.content.Context
4+
import android.content.pm.PackageInfo
5+
import android.os.Build
6+
import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
37
import io.flutter.plugin.common.MethodChannel
8+
import com.aheaditec.freerasp.generated.PackageInfo as FlutterPackageInfo
9+
import com.aheaditec.freerasp.generated.SuspiciousAppInfo as FlutterSuspiciousAppInfo
410

511
/**
612
* Executes the provided block of code and catches any exceptions thrown by it, returning the
@@ -17,3 +23,49 @@ internal inline fun runResultCatching(result: MethodChannel.Result, block: () ->
1723
result.error(err::class.java.name, err.message, null)
1824
}
1925
}
26+
27+
/**
28+
* Converts a [SuspiciousAppInfo] instance to a [com.aheaditec.freerasp.generated.SuspiciousAppInfo]
29+
* instance used by Pigeon package for Flutter.
30+
*
31+
* @return A new [com.aheaditec.freerasp.generated.SuspiciousAppInfo] object with information from
32+
* this [SuspiciousAppInfo].
33+
*/
34+
internal fun SuspiciousAppInfo.toPigeon(context: Context): FlutterSuspiciousAppInfo {
35+
return FlutterSuspiciousAppInfo(this.packageInfo.toPigeon(context), this.reason)
36+
}
37+
38+
/**
39+
* Converts a [PackageInfo] instance to a [com.aheaditec.freerasp.generated.PackageInfo] instance
40+
* used by Pigeon package for Flutter.
41+
*
42+
* @return A new [com.aheaditec.freerasp.generated.PackageInfo] object with information from
43+
* this [PackageInfo].
44+
*/
45+
private fun PackageInfo.toPigeon(context: Context): FlutterPackageInfo {
46+
return FlutterPackageInfo(
47+
packageName = packageName,
48+
appName = applicationInfo?.let {
49+
context.packageManager.getApplicationLabel(it) as String
50+
},
51+
version = getVersionString(),
52+
appIcon = Utils.parseIconBase64(context, packageName),
53+
installationSource = Utils.getInstallerPackageName(context, packageName),
54+
)
55+
}
56+
57+
/**
58+
* Retrieves the version string of the package.
59+
*
60+
* For devices running on Android P (API 28) and above, this method returns the `longVersionCode`.
61+
* For older versions, it returns the `versionCode` (deprecated).
62+
*
63+
* @return A string representation of the version code.
64+
*/
65+
internal fun PackageInfo.getVersionString(): String {
66+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
67+
return longVersionCode.toString()
68+
}
69+
@Suppress("DEPRECATION")
70+
return versionCode.toString()
71+
}

android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter
1717
class FreeraspPlugin : FlutterPlugin, ActivityAware, LifecycleEventObserver {
1818
private var streamHandler: StreamHandler = StreamHandler()
1919
private var methodCallHandler: MethodCallHandler = MethodCallHandler()
20+
2021
private var context: Context? = null
2122
private var lifecycle: Lifecycle? = null
2223

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package com.aheaditec.freerasp
2+
3+
import android.content.Context
4+
import android.graphics.Bitmap
5+
import android.graphics.Canvas
6+
import android.graphics.drawable.BitmapDrawable
7+
import android.graphics.drawable.Drawable
8+
import android.os.Build
9+
import android.util.Base64
10+
import com.aheaditec.talsec_security.security.api.TalsecConfig
11+
import org.json.JSONArray
12+
import org.json.JSONException
13+
import org.json.JSONObject
14+
import java.io.ByteArrayOutputStream
15+
16+
internal object Utils {
17+
@Suppress("ArrayInDataClass")
18+
data class MalwareConfig(
19+
val blacklistedPackageNames: Array<String>,
20+
val blacklistedHashes: Array<String>,
21+
val suspiciousPermissions: Array<Array<String>>,
22+
val whitelistedInstallationSources: Array<String>
23+
)
24+
25+
fun toTalsecConfigThrowing(configJson: String?): TalsecConfig {
26+
if (configJson == null) {
27+
throw JSONException("Configuration is null")
28+
}
29+
30+
val json = JSONObject(configJson)
31+
32+
val watcherMail = json.getString("watcherMail")
33+
val isProd = json.getBoolean("isProd")
34+
val androidConfig = json.getJSONObject("androidConfig")
35+
val packageName = androidConfig.getString("packageName")
36+
val certificateHashes = androidConfig.extractArray<String>("signingCertHashes")
37+
val alternativeStores = androidConfig.extractArray<String>("supportedStores")
38+
val malwareConfig = parseMalwareConfig(androidConfig)
39+
40+
return TalsecConfig.Builder(packageName, certificateHashes)
41+
.watcherMail(watcherMail)
42+
.supportedAlternativeStores(alternativeStores)
43+
.prod(isProd)
44+
.blacklistedPackageNames(malwareConfig.blacklistedPackageNames)
45+
.blacklistedHashes(malwareConfig.blacklistedHashes)
46+
.suspiciousPermissions(malwareConfig.suspiciousPermissions)
47+
.whitelistedInstallationSources(malwareConfig.whitelistedInstallationSources)
48+
.build()
49+
}
50+
51+
private fun parseMalwareConfig(androidConfig: JSONObject): MalwareConfig {
52+
if (!androidConfig.has("malwareConfig")) {
53+
return MalwareConfig(emptyArray(), emptyArray(), emptyArray(), emptyArray())
54+
}
55+
56+
val malwareConfig = androidConfig.getJSONObject("malwareConfig")
57+
58+
return MalwareConfig(
59+
malwareConfig.extractArray("blacklistedPackageNames"),
60+
malwareConfig.extractArray("blacklistedHashes"),
61+
malwareConfig.extractArray<Array<String>>("suspiciousPermissions"),
62+
malwareConfig.extractArray("whitelistedInstallationSources")
63+
)
64+
}
65+
66+
67+
/**
68+
* Retrieves the package name of the installer for a given app package.
69+
*
70+
* @param context The context of the application.
71+
* @param packageName The package name of the app whose installer package name is to be retrieved.
72+
* @return The package name of the installer if available, or `null` if not.
73+
*/
74+
fun getInstallerPackageName(context: Context, packageName: String): String? {
75+
runCatching {
76+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
77+
return context.packageManager.getInstallSourceInfo(packageName).installingPackageName
78+
@Suppress("DEPRECATION")
79+
return context.packageManager.getInstallerPackageName(packageName)
80+
}
81+
return null
82+
}
83+
84+
/**
85+
* Converts the application icon of the specified package into a Base64 encoded string.
86+
*
87+
* @param context The context of the application.
88+
* @param packageName The package name of the app whose icon is to be converted.
89+
* @return A Base64 encoded string representing the app icon.
90+
*/
91+
fun parseIconBase64(context: Context, packageName: String): String? {
92+
val result = runCatching {
93+
val drawable = context.packageManager.getApplicationIcon(packageName)
94+
val bitmap = drawable.toBitmap()
95+
bitmap.toBase64()
96+
}
97+
98+
return result.getOrNull()
99+
}
100+
101+
/**
102+
* Creates a Bitmap from a Drawable object.
103+
*
104+
* @param drawable The Drawable to be converted.
105+
* @return A Bitmap representing the drawable.
106+
*/
107+
private fun createBitmapFromDrawable(drawable: Drawable): Bitmap {
108+
val width = if (drawable.intrinsicWidth > 0) drawable.intrinsicWidth else 1
109+
val height = if (drawable.intrinsicHeight > 0) drawable.intrinsicHeight else 1
110+
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
111+
val canvas = Canvas(bitmap)
112+
113+
drawable.setBounds(0, 0, canvas.width, canvas.height)
114+
drawable.draw(canvas)
115+
116+
return bitmap
117+
}
118+
119+
/**
120+
* Converts a Drawable into a Bitmap.
121+
*
122+
* @receiver The Drawable to be converted.
123+
* @return A Bitmap representing the drawable.
124+
*/
125+
private fun Drawable.toBitmap(): Bitmap {
126+
return when (this) {
127+
is BitmapDrawable -> bitmap
128+
else -> createBitmapFromDrawable(this)
129+
}
130+
}
131+
132+
/**
133+
* Converts a Bitmap into a Base64 encoded string.
134+
*
135+
* @receiver The Bitmap to be converted.
136+
* @return A Base64 encoded string representing the bitmap.
137+
*/
138+
private fun Bitmap.toBase64(): String {
139+
val byteArrayOutputStream = ByteArrayOutputStream()
140+
compress(Bitmap.CompressFormat.PNG, 10, byteArrayOutputStream)
141+
val byteArray = byteArrayOutputStream.toByteArray()
142+
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
143+
}
144+
}
145+
146+
private inline fun <reified T> JSONObject.extractArray(key: String): Array<T> {
147+
return this.optJSONArray(key)?.let { processArray(it) } ?: emptyArray()
148+
}
149+
150+
private inline fun <reified T> processArray(jsonArray: JSONArray): Array<T> {
151+
val list = mutableListOf<T>()
152+
153+
for (i in 0 until jsonArray.length()) {
154+
val element: T = when (T::class) {
155+
String::class -> jsonArray.getString(i) as T
156+
Int::class -> jsonArray.getInt(i) as T
157+
Double::class -> jsonArray.getDouble(i) as T
158+
Boolean::class -> jsonArray.getBoolean(i) as T
159+
Long::class -> jsonArray.getLong(i) as T
160+
Array<String>::class -> {
161+
// Not universal or ideal solution, but should work for our use case
162+
val nestedArray = jsonArray.getJSONArray(i)
163+
val nestedList = mutableListOf<String>()
164+
for (j in 0 until nestedArray.length()) {
165+
nestedList.add(nestedArray.getString(j))
166+
}
167+
nestedList.toTypedArray() as T
168+
}
169+
170+
else -> throw JSONException("Unsupported type")
171+
}
172+
list.add(element)
173+
}
174+
175+
return list.toTypedArray()
176+
}

0 commit comments

Comments
 (0)