Skip to content

Commit

Permalink
Make malware async (#50)
Browse files Browse the repository at this point in the history
* fix(Android): make malware parsing async

* fix(ts): make malware parsing async

* core(example): make malware parsing async

* chore(release): freerasp 7.2.0
  • Loading branch information
tompsota authored Dec 9, 2024
1 parent 8621a60 commit 32f1eed
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 79 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ 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).

## [7.2.0] - 2024-12-06

- iOS SDK version: 6.6.3
- Android SDK version: 13.0.0

### Cordova

#### Changed

- 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
- Parsing of malware data is now async

### Android

#### Changed

- Malware data is now parsed on background thread to improve responsiveness

## [7.1.0] - 2024-11-19

### Cordova
Expand Down
2 changes: 1 addition & 1 deletion example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 14 additions & 4 deletions example/src/app/components/malware-item/malware-item.component.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { Component, Input } from '@angular/core';
import { SuspiciousAppInfo } from 'cordova-talsec-plugin-freerasp';
declare var talsec: any;
import { Component, Input, OnInit } from '@angular/core';
import { SuspiciousAppInfo, Talsec } from 'cordova-talsec-plugin-freerasp';
declare var talsec: Talsec;

@Component({
selector: 'app-malware-item',
templateUrl: './malware-item.component.html',
styleUrls: ['./malware-item.component.css', '../../../theme/variables.css'],
})
export class MalwareItemComponent {
export class MalwareItemComponent implements OnInit {
@Input() susApp!: SuspiciousAppInfo;
expanded = false;

ngOnInit(): void {
this.loadAppIcon();
}

async loadAppIcon(): Promise<void> {
this.susApp.packageInfo.appIcon = await talsec.getAppIcon(
this.susApp.packageInfo.packageName,
);
}

toggleExpanded() {
this.expanded = !this.expanded;
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cordova-talsec-plugin-freerasp",
"version": "7.1.0",
"version": "7.2.0",
"description": "Cordova plugin for improving app security and threat monitoring on Android and iOS mobile devices.",
"cordova": {
"id": "cordova-talsec-plugin-freerasp",
Expand Down
2 changes: 1 addition & 1 deletion plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="cordova-talsec-plugin-freerasp"
version="7.1.0">
version="7.2.0">

<name>freerasp</name>
<author>Talsec (info@talsec.app)</author>
Expand Down
57 changes: 49 additions & 8 deletions src/android/TalsecPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.aheaditec.talsec.cordova

import android.content.Context
import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.util.Log
import com.aheaditec.talsec.cordova.utils.Utils
import com.aheaditec.talsec.cordova.utils.getArraySafe
import com.aheaditec.talsec.cordova.utils.getBooleanSafe
import com.aheaditec.talsec.cordova.utils.getNestedArraySafe
Expand Down Expand Up @@ -47,6 +51,7 @@ class TalsecPlugin : CordovaPlugin() {
"getThreatIdentifiers" -> getThreatIdentifiers(callbackContext)
"getThreatChannelData" -> getThreatChannelData(callbackContext)
"addToWhitelist" -> addToWhitelist(callbackContext, args)
"getAppIcon" -> getAppIcon(callbackContext, args)
else -> {
callbackContext?.error("Talsec plugin executed with unknown action - $action")
return false
Expand Down Expand Up @@ -88,6 +93,24 @@ class TalsecPlugin : CordovaPlugin() {
return true
}

/**
* Method retrieves app icon for the given parameter
* @param packageName package name of the app we want to retrieve icon for
* @return PNG with app icon encoded as a base64 string
*/
fun getAppIcon(callbackContext: CallbackContext?, args: JSONArray?): Boolean {
val packageName = args?.optString(0, null) ?: run {
callbackContext?.error("Missing packageName parameter in Talsec Native Plugin")
return false
}
// Perform the app icon encoding on a background thread
backgroundHandler.post {
val encodedData = Utils.getAppIconAsBase64String(cordova.context, packageName)
mainHandler.post { callbackContext?.success(encodedData) }
}
return true
}

override fun onPause(multitasking: Boolean) {
super.onPause(multitasking)
if (this.cordova.activity.isFinishing) {
Expand All @@ -104,6 +127,12 @@ class TalsecPlugin : CordovaPlugin() {
}
}

override fun onDestroy() {
super.onDestroy()

backgroundHandlerThread.quitSafely()
}

/**
* We never send an invalid callback over our channel.
* Therefore, if this happens, we want to kill the app.
Expand Down Expand Up @@ -166,23 +195,35 @@ class TalsecPlugin : CordovaPlugin() {
companion object {
private var callback: CallbackContext? = null

val THREAT_CHANNEL_KEY = (10000..999999999).random()
private val THREAT_CHANNEL_KEY = (10000..999999999).random()
.toString() // key of the argument map under which threats are expected
val MALWARE_CHANNEL_KEY = (10000..999999999).random()
private val MALWARE_CHANNEL_KEY = (10000..999999999).random()
.toString() // key of the argument map under which malware data is expected

private lateinit var appContext: Context
private val backgroundHandlerThread = HandlerThread("BackgroundThread").apply { start() }
private val backgroundHandler = Handler(backgroundHandlerThread.looper)
private val mainHandler = Handler(Looper.getMainLooper())


/**
* Sends malware detected event to Cordova
*/
private fun notifyMalware(suspiciousApps: MutableList<SuspiciousAppInfo>) {
val response = JSONObject()
response.put(THREAT_CHANNEL_KEY, Threat.Malware.value)
response.put(MALWARE_CHANNEL_KEY, suspiciousApps.toEncodedJsonArray(appContext))
val result = PluginResult(PluginResult.Status.OK, response)
result.keepCallback = true
callback?.sendPluginResult(result) ?: Log.w("TalsecPlugin", "Listener not registered.")
// Perform the malware encoding on a background thread
backgroundHandler.post {

val encodedSuspiciousApps = suspiciousApps.toEncodedJsonArray(appContext)

mainHandler.post {
val response = JSONObject()
response.put(THREAT_CHANNEL_KEY, Threat.Malware.value)
response.put(MALWARE_CHANNEL_KEY, encodedSuspiciousApps)
val result = PluginResult(PluginResult.Status.OK, response)
result.keepCallback = true
callback?.sendPluginResult(result) ?: Log.w("TalsecPlugin", "Listener not registered.")
}
}
}

private fun notifyThreat(threat: Threat) {
Expand Down
2 changes: 1 addition & 1 deletion src/android/utils/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ internal fun PackageInfo.toCordovaPackageInfo(context: Context): CordovaPackageI
packageName = this.packageName,
appName = Utils.getAppName(context, this.applicationInfo),
version = this.versionName,
appIcon = Utils.getAppIconAsBase64String(context, this.packageName),
appIcon = null, // this requires heavier computations, so appIcon has to be retrieved separately
installerStore = Utils.getInstallationSource(context, this.packageName)
)
}
Expand Down
5 changes: 4 additions & 1 deletion src/android/utils/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ internal object Utils {
context.packageManager.getInstallerPackageName(packageName)
}
} catch (e: Exception) {
Log.e("Talsec", "Could not retrieve app installation source for ${packageName}: ${e.message}")
Log.e(
"Talsec",
"Could not retrieve app installation source for ${packageName}: ${e.message}"
)
null
}
}
Expand Down
1 change: 1 addition & 0 deletions www/talsec.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface Talsec {
eventListenerConfig: NativeEventEmitterActions,
) => Promise<void>;
addToWhitelist: (packageName: string) => Promise<string>;
getAppIcon: (packageName: string) => Promise<string>;
}
export type SuspiciousAppInfo = {
packageInfo: PackageInfo;
Expand Down
45 changes: 14 additions & 31 deletions www/talsec.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,7 @@ const getThreatCount = () => {
const getThreatChannelData = async () => {
const dataLength = cordova.platformId === 'ios' ? 1 : 2;
const data = await new Promise((resolve, reject) => {
cordova.exec(
(data) => {
resolve(data);
},
(error) => {
reject(error);
},
'TalsecPlugin',
'getThreatChannelData',
);
cordova.exec(resolve, reject, 'TalsecPlugin', 'getThreatChannelData');
});
if (data.length !== dataLength || !itemsHaveType(data, 'string')) {
onInvalidCallback();
Expand All @@ -80,16 +71,7 @@ const itemsHaveType = (data, desidedType) => {
};
const getThreatIdentifiers = async () => {
const identifiers = await new Promise((resolve, reject) => {
cordova.exec(
(data) => {
resolve(data);
},
(error) => {
reject(error);
},
'TalsecPlugin',
'getThreatIdentifiers',
);
cordova.exec(resolve, reject, 'TalsecPlugin', 'getThreatIdentifiers');
});
if (
identifiers.length !== getThreatCount() ||
Expand Down Expand Up @@ -203,21 +185,22 @@ const addToWhitelist = (packageName) => {
return Promise.reject('Malware detection not available on iOS');
}
return new Promise((resolve, reject) => {
cordova.exec(
(response) => {
resolve(response);
},
(error) => {
reject(error);
},
'TalsecPlugin',
'addToWhitelist',
[packageName],
);
cordova.exec(resolve, reject, 'TalsecPlugin', 'addToWhitelist', [
packageName,
]);
});
};
const getAppIcon = (packageName) => {
if (cordova.platformId === 'ios') {
return Promise.reject('Malware detection not available on iOS');
}
return new Promise((resolve, reject) => {
cordova.exec(resolve, reject, 'TalsecPlugin', 'getAppIcon', [packageName]);
});
};
// @ts-ignore
module.exports = {
start,
addToWhitelist,
getAppIcon,
};
47 changes: 16 additions & 31 deletions www/talsec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface Talsec {
eventListenerConfig: NativeEventEmitterActions,
) => Promise<void>;
addToWhitelist: (packageName: string) => Promise<string>;
getAppIcon: (packageName: string) => Promise<string>;
}

export type SuspiciousAppInfo = {
Expand Down Expand Up @@ -130,16 +131,7 @@ const getThreatCount = (): number => {
const getThreatChannelData = async (): Promise<[string, string]> => {
const dataLength = cordova.platformId === 'ios' ? 1 : 2;
const data: [string, string] = await new Promise((resolve, reject) => {
cordova.exec(
(data: [string, string]) => {
resolve(data);
},
(error: any) => {
reject(error);
},
'TalsecPlugin',
'getThreatChannelData',
);
cordova.exec(resolve, reject, 'TalsecPlugin', 'getThreatChannelData');
});
if (data.length !== dataLength || !itemsHaveType(data, 'string')) {
onInvalidCallback();
Expand All @@ -154,16 +146,7 @@ const itemsHaveType = (data: any[], desidedType: string) => {

const getThreatIdentifiers = async (): Promise<number[]> => {
const identifiers: number[] = await new Promise((resolve, reject) => {
cordova.exec(
(data: number[]) => {
resolve(data);
},
(error: any) => {
reject(error);
},
'TalsecPlugin',
'getThreatIdentifiers',
);
cordova.exec(resolve, reject, 'TalsecPlugin', 'getThreatIdentifiers');
});
if (
identifiers.length !== getThreatCount() ||
Expand Down Expand Up @@ -288,22 +271,24 @@ const addToWhitelist = (packageName: string): Promise<string> => {
return Promise.reject('Malware detection not available on iOS');
}
return new Promise((resolve, reject) => {
cordova.exec(
(response: string) => {
resolve(response);
},
(error: any) => {
reject(error);
},
'TalsecPlugin',
'addToWhitelist',
[packageName],
);
cordova.exec(resolve, reject, 'TalsecPlugin', 'addToWhitelist', [
packageName,
]);
});
};

const getAppIcon = (packageName: string): Promise<string> => {
if (cordova.platformId === 'ios') {
return Promise.reject('Malware detection not available on iOS');
}
return new Promise((resolve, reject) => {
cordova.exec(resolve, reject, 'TalsecPlugin', 'getAppIcon', [packageName]);
});
};

// @ts-ignore
module.exports = {
start,
addToWhitelist,
getAppIcon,
};

0 comments on commit 32f1eed

Please sign in to comment.