Skip to content

Commit 3757791

Browse files
authored
Make malware async (#98)
* fe=ix(Android): make malware parsing async * fix(ts): make malware parsing async * core(example): make malware parsing async * chore(release): freerasp 3.12.0 * chore: bug fixes * chore(Android): unregister listeners on destroy * chore(Android): remove unregister listeners
1 parent 48e2553 commit 3757791

File tree

7 files changed

+118
-34
lines changed

7 files changed

+118
-34
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ 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+
## [3.12.0] - 2024-12-06
9+
10+
- iOS SDK version: 6.6.3
11+
- Android SDK version: 13.0.0
12+
13+
### React Native
14+
15+
#### Changed
16+
17+
- App icons for detected malware are not fetched automatically anymore, which reduces computation required to retrieve malware data. From now on, app icons have to be retrieved using the `getAppIcon` method
18+
- Parsing of malware data is now async
19+
20+
### Android
21+
22+
#### Changed
23+
24+
- Malware data is now parsed on background thread to improve responsiveness
25+
826
## [3.11.0] - 2024-11-19
927

1028
### React Native

android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package com.freeraspreactnative
22

3+
import android.os.Handler
4+
import android.os.HandlerThread
5+
import android.os.Looper
36
import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
47
import com.aheaditec.talsec_security.security.api.Talsec
58
import com.aheaditec.talsec_security.security.api.TalsecConfig
69
import com.aheaditec.talsec_security.security.api.ThreatListener
710
import com.facebook.react.bridge.Arguments
11+
import com.facebook.react.bridge.LifecycleEventListener
812
import com.facebook.react.bridge.Promise
913
import com.facebook.react.bridge.ReactApplicationContext
1014
import com.facebook.react.bridge.ReactContextBaseJavaModule
@@ -13,6 +17,7 @@ import com.facebook.react.bridge.ReadableMap
1317
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
1418
import com.facebook.react.bridge.WritableArray
1519
import com.facebook.react.modules.core.DeviceEventManagerModule
20+
import com.freeraspreactnative.utils.Utils
1621
import com.freeraspreactnative.utils.getArraySafe
1722
import com.freeraspreactnative.utils.getBooleanSafe
1823
import com.freeraspreactnative.utils.getMapThrowing
@@ -24,13 +29,27 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
2429
ReactContextBaseJavaModule(reactContext) {
2530

2631
private val listener = ThreatListener(FreeraspThreatHandler, FreeraspThreatHandler)
32+
private val lifecycleListener = object : LifecycleEventListener {
33+
override fun onHostResume() {
34+
// do nothing
35+
}
36+
37+
override fun onHostPause() {
38+
// do nothing
39+
}
40+
41+
override fun onHostDestroy() {
42+
backgroundHandlerThread.quitSafely()
43+
}
44+
}
2745

2846
override fun getName(): String {
2947
return NAME
3048
}
3149

3250
init {
3351
appReactContext = reactContext
52+
reactContext.addLifecycleEventListener(lifecycleListener)
3453
}
3554

3655
@ReactMethod
@@ -103,6 +122,20 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
103122
promise.resolve(true)
104123
}
105124

125+
/**
126+
* Method retrieves app icon for the given parameter
127+
* @param packageName package name of the app we want to retrieve icon for
128+
* @return PNG with app icon encoded as a base64 string
129+
*/
130+
@ReactMethod
131+
fun getAppIcon(packageName: String, promise: Promise) {
132+
// Perform the app icon encoding on a background thread
133+
backgroundHandler.post {
134+
val encodedData = Utils.getAppIconAsBase64String(reactContext, packageName)
135+
mainHandler.post { promise.resolve(encodedData) }
136+
}
137+
}
138+
106139
private fun buildTalsecConfig(config: ReadableMap): TalsecConfig {
107140
val androidConfig = config.getMapThrowing("androidConfig")
108141
val packageName = androidConfig.getStringThrowing("packageName")
@@ -126,13 +159,19 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
126159

127160
companion object {
128161
const val NAME = "FreeraspReactNative"
129-
val THREAT_CHANNEL_NAME = (10000..999999999).random()
162+
private val THREAT_CHANNEL_NAME = (10000..999999999).random()
130163
.toString() // name of the channel over which threat callbacks are sent
131-
val THREAT_CHANNEL_KEY = (10000..999999999).random()
164+
private val THREAT_CHANNEL_KEY = (10000..999999999).random()
132165
.toString() // key of the argument map under which threats are expected
133-
val MALWARE_CHANNEL_KEY = (10000..999999999).random()
166+
private val MALWARE_CHANNEL_KEY = (10000..999999999).random()
134167
.toString() // key of the argument map under which malware data is expected
168+
169+
private val backgroundHandlerThread = HandlerThread("BackgroundThread").apply { start() }
170+
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
171+
private val mainHandler = Handler(Looper.getMainLooper())
172+
135173
private lateinit var appReactContext: ReactApplicationContext
174+
136175
private fun notifyListeners(threat: Threat) {
137176
val params = Arguments.createMap()
138177
params.putInt(THREAT_CHANNEL_KEY, threat.value)
@@ -145,15 +184,23 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
145184
* Sends malware detected event to React Native
146185
*/
147186
private fun notifyMalware(suspiciousApps: MutableList<SuspiciousAppInfo>) {
148-
val params = Arguments.createMap()
149-
params.putInt(THREAT_CHANNEL_KEY, Threat.Malware.value)
150-
params.putArray(
151-
MALWARE_CHANNEL_KEY, suspiciousApps.toEncodedWritableArray(appReactContext)
152-
)
153-
154-
appReactContext
155-
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
156-
.emit(THREAT_CHANNEL_NAME, params)
187+
// Perform the malware encoding on a background thread
188+
backgroundHandler.post {
189+
190+
val encodedSuspiciousApps = suspiciousApps.toEncodedWritableArray(appReactContext)
191+
192+
mainHandler.post {
193+
val params = Arguments.createMap()
194+
params.putInt(THREAT_CHANNEL_KEY, Threat.Malware.value)
195+
params.putArray(
196+
MALWARE_CHANNEL_KEY, encodedSuspiciousApps
197+
)
198+
199+
appReactContext
200+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
201+
.emit(THREAT_CHANNEL_NAME, params)
202+
}
203+
}
157204
}
158205
}
159206

android/src/main/java/com/freeraspreactnative/utils/Extensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ internal fun PackageInfo.toRNPackageInfo(context: ReactContext): RNPackageInfo {
8686
packageName = this.packageName,
8787
appName = Utils.getAppName(context, this.applicationInfo),
8888
version = this.versionName,
89-
appIcon = Utils.getAppIconAsBase64String(context, this.packageName),
89+
appIcon = null, // this requires heavier computations, so appIcon has to be retrieved separately
9090
installerStore = Utils.getInstallationSource(context, this.packageName)
9191
)
9292
}

android/src/main/java/com/freeraspreactnative/utils/Utils.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ internal object Utils {
7575
context.packageManager.getInstallerPackageName(packageName)
7676
}
7777
} catch (e: Exception) {
78-
Log.e("Talsec", "Could not retrieve app installation source for ${packageName}: ${e.message}")
78+
Log.e(
79+
"Talsec",
80+
"Could not retrieve app installation source for ${packageName}: ${e.message}",
81+
)
7982
null
8083
}
8184
}

example/src/MalwareItem.tsx

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
import { Button, HStack } from '@react-native-material/core';
2-
import { addToWhitelist, type SuspiciousAppInfo } from 'freerasp-react-native';
2+
import {
3+
addToWhitelist,
4+
getAppIcon,
5+
type SuspiciousAppInfo,
6+
} from 'freerasp-react-native';
37
import ArrowUp from '../assets/arrow-up.png';
48
import ArrowDown from '../assets/arrow-down.png';
5-
import React from 'react';
9+
import React, { useEffect } from 'react';
610
import { useState } from 'react';
711
import { TouchableOpacity, View, Text, Image, StyleSheet } from 'react-native';
812
import { Colors } from './styles';
913

1014
export const MalwareItem: React.FC<{ app: SuspiciousAppInfo }> = ({ app }) => {
1115
const [expanded, setExpanded] = useState(false);
1216

17+
useEffect(() => {
18+
(async () => {
19+
// retrieve app icons for detected malware
20+
const appIcon = await getAppIcon(app.packageInfo.packageName);
21+
app.packageInfo.appIcon = appIcon;
22+
})();
23+
}, [app.packageInfo]);
24+
1325
const appUninstall = async () => {
1426
alert('Implement yourself!');
1527
};
@@ -94,19 +106,6 @@ export const MalwareItem: React.FC<{ app: SuspiciousAppInfo }> = ({ app }) => {
94106
};
95107

96108
const styles = StyleSheet.create({
97-
button: {
98-
borderRadius: 20,
99-
paddingHorizontal: 30,
100-
paddingVertical: 10,
101-
marginTop: 15,
102-
elevation: 2,
103-
},
104-
buttonOpen: {
105-
backgroundColor: '#F194FF',
106-
},
107-
buttonClose: {
108-
backgroundColor: '#2196F3',
109-
},
110109
item: {
111110
backgroundColor: '#d4e4ff',
112111
borderRadius: 20,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "freerasp-react-native",
3-
"version": "3.11.0",
3+
"version": "3.12.0",
44
"description": "React Native plugin for improving app security and threat monitoring on Android and iOS mobile devices.",
55
"main": "lib/commonjs/index",
66
"module": "lib/module/index",

src/index.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,16 @@ const prepareMapping = async (): Promise<void> => {
5454
};
5555

5656
// parses base64-encoded malware data to SuspiciousAppInfo[]
57-
const parseMalwareData = (data: string[]): SuspiciousAppInfo[] => {
58-
return data.map((entry) => toSuspiciousAppInfo(entry));
57+
const parseMalwareData = async (
58+
data: string[]
59+
): Promise<SuspiciousAppInfo[]> => {
60+
return new Promise((resolve, reject) => {
61+
try {
62+
resolve(data.map((entry) => toSuspiciousAppInfo(entry)));
63+
} catch (error: any) {
64+
reject(`Error while parsing app data: ${error}`);
65+
}
66+
});
5967
};
6068

6169
const toSuspiciousAppInfo = (base64Value: string): SuspiciousAppInfo => {
@@ -70,7 +78,7 @@ export const setThreatListeners = async <T extends NativeEventEmitterActions>(
7078
const [channel, key, malwareKey] = await getThreatChannelData();
7179
await prepareMapping();
7280

73-
eventsListener = eventEmitter.addListener(channel, (event) => {
81+
eventsListener = eventEmitter.addListener(channel, async (event) => {
7482
if (event[key] === undefined) {
7583
onInvalidCallback();
7684
}
@@ -115,7 +123,7 @@ export const setThreatListeners = async <T extends NativeEventEmitterActions>(
115123
config.systemVPN?.();
116124
break;
117125
case Threat.Malware.value:
118-
config.malware?.(parseMalwareData(event[malwareKey]));
126+
config.malware?.(await parseMalwareData(event[malwareKey]));
119127
break;
120128
case Threat.ADBEnabled.value:
121129
config.adbEnabled?.();
@@ -167,5 +175,14 @@ export const addToWhitelist = async (packageName: string): Promise<boolean> => {
167175
return FreeraspReactNative.addToWhitelist(packageName);
168176
};
169177

178+
export const getAppIcon = (packageName: string): Promise<string> => {
179+
if (Platform.OS === 'ios') {
180+
return Promise.reject(
181+
'App icon retrieval for Malware detection not available on iOS'
182+
);
183+
}
184+
return FreeraspReactNative.getAppIcon(packageName);
185+
};
186+
170187
export * from './types';
171188
export default FreeraspReactNative;

0 commit comments

Comments
 (0)