Skip to content

Commit 5db8538

Browse files
authoredDec 16, 2024··
Make malware async (#25)
* fix(Android): move malware processing to background thread * fix(ts): move malware processing to background thread * feat(example): move malware processing to background thread * docs: update changelog * chore(release): freeRASP 1.8.0 * chore: bug fix
1 parent 86b27cd commit 5db8538

File tree

14 files changed

+151
-30
lines changed

14 files changed

+151
-30
lines changed
 

‎CHANGELOG.md

+18
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+
## [1.8.0] - 2024-12-06
9+
10+
- iOS SDK version: 6.6.3
11+
- Android SDK version: 13.0.0
12+
13+
### Capacitor
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
## [1.7.0] - 2024-11-19
927

1028
### Capacitor

‎android/src/main/java/com/aheaditec/freerasp/FreeraspPlugin.kt

+51-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package com.aheaditec.freerasp
22

3+
import android.os.Handler
4+
import android.os.HandlerThread
5+
import android.os.Looper
6+
import com.aheaditec.freerasp.utils.Utils
37
import com.aheaditec.freerasp.utils.getArraySafe
48
import com.aheaditec.freerasp.utils.getNestedArraySafe
59
import com.aheaditec.freerasp.utils.toEncodedJSArray
@@ -37,7 +41,11 @@ class FreeraspPlugin : Plugin() {
3741
}
3842
call.resolve(JSObject().put("started", true))
3943
} catch (e: Exception) {
40-
call.reject("Error during Talsec Native plugin initialization - ${e.message}", "TalsecInitializationError", e)
44+
call.reject(
45+
"Error during Talsec Native plugin initialization - ${e.message}",
46+
"TalsecInitializationError",
47+
e
48+
)
4149
}
4250
}
4351

@@ -57,6 +65,11 @@ class FreeraspPlugin : Plugin() {
5765
}
5866
}
5967

68+
override fun handleOnDestroy() {
69+
super.handleOnDestroy()
70+
backgroundHandlerThread.quitSafely()
71+
}
72+
6073
/**
6174
* Method to get the random identifiers of callbacks
6275
*/
@@ -107,13 +120,44 @@ class FreeraspPlugin : Plugin() {
107120
call.resolve(JSObject().put("result", true))
108121
}
109122

123+
/**
124+
* Method retrieves app icon for the given parameter
125+
* @param packageName package name of the app we want to retrieve icon for
126+
* @return PNG with app icon encoded as a base64 string
127+
*/
128+
@PluginMethod
129+
fun getAppIcon(call: PluginCall) {
130+
val packageName = call.getString("packageName")
131+
if (packageName.isNullOrEmpty()) {
132+
call.reject(
133+
"Package name argument is missing or empty in the call",
134+
"MissingArgumentError"
135+
)
136+
return
137+
}
138+
// Perform the app icon encoding on a background thread
139+
backgroundHandler.post {
140+
val encodedData = Utils.getAppIconAsBase64String(context, packageName)
141+
mainHandler.post { call.resolve(JSObject().put("result", encodedData)) }
142+
}
143+
}
144+
110145
internal fun notifyListeners(threat: Threat) {
111146
notifyListeners(THREAT_CHANNEL_NAME, JSObject().put(THREAT_CHANNEL_KEY, threat.value), true)
112147
}
113148

114149
internal fun notifyMalware(suspiciousApps: MutableList<SuspiciousAppInfo>) {
115-
notifyListeners(THREAT_CHANNEL_NAME, JSObject().put(THREAT_CHANNEL_KEY, Threat.Malware.value).put(
116-
MALWARE_CHANNEL_KEY, suspiciousApps.toEncodedJSArray(context)), true)
150+
// Perform the malware encoding on a background thread
151+
backgroundHandler.post {
152+
153+
val encodedSuspiciousApps = suspiciousApps.toEncodedJSArray(context)
154+
mainHandler.post {
155+
val params = JSObject()
156+
.put(THREAT_CHANNEL_KEY, Threat.Malware.value)
157+
.put(MALWARE_CHANNEL_KEY, encodedSuspiciousApps)
158+
notifyListeners(THREAT_CHANNEL_NAME, params, true)
159+
}
160+
}
117161
}
118162

119163
private fun buildTalsecConfigThrowing(configJson: JSObject): TalsecConfig {
@@ -140,7 +184,10 @@ class FreeraspPlugin : Plugin() {
140184
.toString() // name of the channel over which threat callbacks are sent
141185
private val THREAT_CHANNEL_KEY = (10000..999999999).random()
142186
.toString() // key of the argument map under which threats are expected
143-
val MALWARE_CHANNEL_KEY = (10000..999999999).random()
187+
private val MALWARE_CHANNEL_KEY = (10000..999999999).random()
144188
.toString() // key of the argument map under which malware data is expected
189+
private val backgroundHandlerThread = HandlerThread("BackgroundThread").apply { start() }
190+
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
191+
private val mainHandler = Handler(Looper.getMainLooper())
145192
}
146193
}

‎android/src/main/java/com/aheaditec/freerasp/utils/Extensions.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ internal fun PackageInfo.toCapPackageInfo(context: Context): CapPackageInfo {
6868
packageName = this.packageName,
6969
appName = Utils.getAppName(context, this.applicationInfo),
7070
version = this.versionName,
71-
appIcon = Utils.getAppIconAsBase64String(context, this.packageName),
71+
appIcon = null, // this requires heavier computations, so appIcon has to be retrieved separately
7272
installerStore = Utils.getInstallationSource(context, this.packageName)
7373
)
7474
}

‎android/src/main/java/com/aheaditec/freerasp/utils/Utils.kt

+4-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ internal object Utils {
7474
context.packageManager.getInstallerPackageName(packageName)
7575
}
7676
} catch (e: Exception) {
77-
Log.e("Talsec", "Could not retrieve app installation source for ${packageName}: ${e.message}")
77+
Log.e(
78+
"Talsec",
79+
"Could not retrieve app installation source for ${packageName}: ${e.message}"
80+
)
7881
null
7982
}
8083
}

‎dist/esm/definitions.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export interface FreeraspPlugin {
1717
}): Promise<{
1818
result: boolean;
1919
}>;
20+
getAppIcon(options: {
21+
packageName: string;
22+
}): Promise<{
23+
result: string;
24+
}>;
2025
}
2126
export type FreeraspConfig = {
2227
androidConfig?: AndroidConfig;

‎dist/esm/definitions.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/esm/index.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ declare const setThreatListeners: <T extends NativeEventEmitterActions>(callback
44
declare const removeThreatListeners: () => void;
55
declare const startFreeRASP: <T extends NativeEventEmitterActions>(config: FreeraspConfig, reactions: T & Record<Exclude<keyof T, keyof NativeEventEmitterActions>, []>) => Promise<boolean>;
66
declare const addToWhitelist: (packageName: string) => Promise<boolean>;
7+
declare const getAppIcon: (packageName: string) => Promise<string>;
78
export * from './definitions';
8-
export { Freerasp, startFreeRASP, setThreatListeners, removeThreatListeners, addToWhitelist, };
9+
export { Freerasp, startFreeRASP, setThreatListeners, removeThreatListeners, addToWhitelist, getAppIcon, };

‎dist/esm/index.js

+20-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/esm/index.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎example/src/components/MalwareItem.css

+8-6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@
33
font-weight: 600;
44
color: grey;
55
}
6+
67
.listItem {
78
font-size: 1rem;
89
padding-bottom: 5;
910
color: grey;
1011
}
12+
13+
.malwareCard {
14+
width: 100%;
15+
border-radius: 1.5rem;
16+
background-color: rgb(218, 242, 249);
17+
}
18+
1119
.icon {
1220
width: 2.5rem;
1321
height: 2.5rem;
@@ -23,12 +31,6 @@
2331
height: 3.5rem;
2432
}
2533

26-
.malwareCard {
27-
width: 100%;
28-
border-radius: 1.5rem;
29-
background-color: rgb(218, 242, 249);
30-
}
31-
3234
.malwareCardInner {
3335
margin: 0;
3436
padding: 0;

‎example/src/components/MalwareItem.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { addToWhitelist, SuspiciousAppInfo } from 'capacitor-freerasp';
2-
import React from 'react';
1+
import { addToWhitelist, getAppIcon } from 'capacitor-freerasp';
2+
import type { SuspiciousAppInfo } from 'capacitor-freerasp';
3+
import React, { useEffect } from 'react';
34
import { useState } from 'react';
45
import {
56
IonText,
@@ -16,7 +17,15 @@ import { chevronDown, chevronUp } from 'ionicons/icons';
1617

1718
export const MalwareItem: React.FC<{ app: SuspiciousAppInfo }> = ({ app }) => {
1819
const [expanded, setExpanded] = useState<boolean>(false);
20+
const [appIcon, setAppIcon] = React.useState<string>('');
1921

22+
useEffect(() => {
23+
(async () => {
24+
// retrieve app icons for detected malware
25+
const appIcon = await getAppIcon(app.packageInfo.packageName);
26+
setAppIcon(appIcon);
27+
})();
28+
}, []);
2029
const appUninstall = async () => {
2130
alert('Implement yourself!');
2231
};
@@ -43,7 +52,7 @@ export const MalwareItem: React.FC<{ app: SuspiciousAppInfo }> = ({ app }) => {
4352
<IonRow>
4453
<IonImg
4554
className="icon centered"
46-
src={`data:image/png;base64,${app.packageInfo.appIcon}`}
55+
src={`data:image/png;base64,${appIcon}`}
4756
/>
4857
<div className="titleTextContainer">
4958
<IonText className="titleText">{app.packageInfo.appName}</IonText>
@@ -77,10 +86,10 @@ export const MalwareItem: React.FC<{ app: SuspiciousAppInfo }> = ({ app }) => {
7786
</IonText>
7887
<br />
7988
<IonText className="listItemTitle">App Icon:</IonText>
80-
{app.packageInfo.appIcon ? (
89+
{appIcon ? (
8190
<IonImg
8291
className="icon iconBig"
83-
src={`data:image/png;base64,${app.packageInfo.appIcon}`}
92+
src={`data:image/png;base64,${appIcon}`}
8493
/>
8594
) : (
8695
<>

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "capacitor-freerasp",
3-
"version": "1.7.0",
3+
"version": "1.8.0",
44
"description": "Capacitor plugin for improving app security and threat monitoring on Android and iOS mobile devices",
55
"main": "dist/plugin.cjs.js",
66
"module": "dist/esm/index.js",

‎src/definitions.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface FreeraspPlugin {
1111
addToWhitelist(options: {
1212
packageName: string;
1313
}): Promise<{ result: boolean }>;
14+
getAppIcon(options: { packageName: string }): Promise<{ result: string }>;
1415
}
1516

1617
export type FreeraspConfig = {

‎src/index.ts

+24-4
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,17 @@ const prepareMapping = async (): Promise<void> => {
4545
};
4646

4747
// parses base64-encoded malware data to SuspiciousAppInfo[]
48-
const parseMalwareData = (data: string[]): SuspiciousAppInfo[] => {
49-
return data.map(entry => toSuspiciousAppInfo(entry));
48+
const parseMalwareData = async (
49+
data: string[],
50+
): Promise<SuspiciousAppInfo[]> => {
51+
return new Promise((resolve, reject) => {
52+
try {
53+
const suspiciousAppData = data.map(entry => toSuspiciousAppInfo(entry));
54+
resolve(suspiciousAppData);
55+
} catch (error: any) {
56+
reject(`Parsing app data failed: ${error}`);
57+
}
58+
});
5059
};
5160

5261
const toSuspiciousAppInfo = (base64Value: string): SuspiciousAppInfo => {
@@ -61,7 +70,7 @@ const setThreatListeners = async <T extends NativeEventEmitterActions>(
6170
const [channel, key, malwareKey] = await getThreatChannelData();
6271
await prepareMapping();
6372

64-
await Freerasp.addListener(channel, (event: any) => {
73+
await Freerasp.addListener(channel, async (event: any) => {
6574
if (event[key] === undefined) {
6675
onInvalidCallback();
6776
}
@@ -106,7 +115,7 @@ const setThreatListeners = async <T extends NativeEventEmitterActions>(
106115
callbacks.systemVPN?.();
107116
break;
108117
case Threat.Malware.value:
109-
callbacks.malware?.(parseMalwareData(event[malwareKey]));
118+
callbacks.malware?.(await parseMalwareData(event[malwareKey]));
110119
break;
111120
case Threat.ADBEnabled.value:
112121
callbacks.adbEnabled?.();
@@ -144,11 +153,22 @@ const addToWhitelist = async (packageName: string): Promise<boolean> => {
144153
return result;
145154
};
146155

156+
const getAppIcon = async (packageName: string): Promise<string> => {
157+
if (Capacitor.getPlatform() === 'ios') {
158+
return Promise.reject(
159+
'App icon retrieval for Malware detection not available on iOS',
160+
);
161+
}
162+
const { result } = await Freerasp.getAppIcon({ packageName });
163+
return result;
164+
};
165+
147166
export * from './definitions';
148167
export {
149168
Freerasp,
150169
startFreeRASP,
151170
setThreatListeners,
152171
removeThreatListeners,
153172
addToWhitelist,
173+
getAppIcon,
154174
};

0 commit comments

Comments
 (0)
Please sign in to comment.