Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 3.10.0 #88

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ buildscript {
classpath "com.android.tools.build:gradle:7.2.1"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}

Expand All @@ -20,6 +21,7 @@ def isNewArchitectureEnabled() {

apply plugin: "com.android.library"
apply plugin: "kotlin-android"
apply plugin: 'kotlinx-serialization'

if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
Expand Down Expand Up @@ -87,6 +89,7 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:$react_native_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:11.1.1"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.freeraspreactnative

import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
import com.aheaditec.talsec_security.security.api.Talsec
import com.aheaditec.talsec_security.security.api.TalsecConfig
import com.aheaditec.talsec_security.security.api.ThreatListener
Expand All @@ -12,8 +13,14 @@ import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
import com.facebook.react.bridge.WritableArray
import com.facebook.react.modules.core.DeviceEventManagerModule

class FreeraspReactNativeModule(val reactContext: ReactApplicationContext) :
import com.freeraspreactnative.utils.getArraySafe
import com.freeraspreactnative.utils.getBooleanSafe
import com.freeraspreactnative.utils.getMapThrowing
import com.freeraspreactnative.utils.getNestedArraySafe
import com.freeraspreactnative.utils.getStringThrowing
import com.freeraspreactnative.utils.toEncodedWritableArray

class FreeraspReactNativeModule(private val reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {

private val listener = ThreatListener(FreeraspThreatHandler, FreeraspThreatHandler)
Expand Down Expand Up @@ -42,8 +49,7 @@ class FreeraspReactNativeModule(val reactContext: ReactApplicationContext) :

promise.resolve("freeRASP started")

}
catch (e: Exception) {
} catch (e: Exception) {
promise.reject("TalsecInitializationError", e.message, e)
}
}
Expand All @@ -65,6 +71,7 @@ class FreeraspReactNativeModule(val reactContext: ReactApplicationContext) :
val channelData: WritableArray = Arguments.createArray()
channelData.pushString(THREAT_CHANNEL_NAME)
channelData.pushString(THREAT_CHANNEL_KEY)
channelData.pushString(MALWARE_CHANNEL_KEY)
promise.resolve(channelData)
}

Expand All @@ -87,6 +94,15 @@ class FreeraspReactNativeModule(val reactContext: ReactApplicationContext) :
// Remove upstream listeners, stop unnecessary background tasks
}

/**
* Method to add apps to Malware whitelist, so they don't get flagged as malware
*/
@ReactMethod
fun addToWhitelist(packageName: String, promise: Promise) {
Talsec.addToWhitelist(reactContext, packageName)
promise.resolve(true)
}

private fun buildTalsecConfig(config: ReadableMap): TalsecConfig {
val androidConfig = config.getMapThrowing("androidConfig")
val packageName = androidConfig.getStringThrowing("packageName")
Expand All @@ -97,6 +113,14 @@ class FreeraspReactNativeModule(val reactContext: ReactApplicationContext) :
.supportedAlternativeStores(androidConfig.getArraySafe("supportedAlternativeStores"))
.prod(config.getBooleanSafe("isProd"))

if (androidConfig.hasKey("malware")) {
val malwareConfig = androidConfig.getMapThrowing("malware")
talsecBuilder.whitelistedInstallationSources(malwareConfig.getArraySafe("whitelistedInstallationSources"))
talsecBuilder.blocklistedHashes(malwareConfig.getArraySafe("blocklistedHashes"))
talsecBuilder.blocklistedPermissions(malwareConfig.getNestedArraySafe("blocklistedPermissions"))
talsecBuilder.blocklistedPackageNames(malwareConfig.getArraySafe("blocklistedPackageNames"))
}

return talsecBuilder.build()
}

Expand All @@ -106,6 +130,8 @@ class FreeraspReactNativeModule(val reactContext: ReactApplicationContext) :
.toString() // name of the channel over which threat callbacks are sent
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()
.toString() // key of the argument map under which malware data is expected
private lateinit var appReactContext: ReactApplicationContext
private fun notifyListeners(threat: Threat) {
val params = Arguments.createMap()
Expand All @@ -114,11 +140,30 @@ class FreeraspReactNativeModule(val reactContext: ReactApplicationContext) :
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(THREAT_CHANNEL_NAME, params)
}

/**
* Sends malware detected event to React Native
*/
private fun notifyMalware(suspiciousApps: MutableList<SuspiciousAppInfo>) {
val params = Arguments.createMap()
params.putInt(THREAT_CHANNEL_KEY, Threat.Malware.value)
params.putArray(
MALWARE_CHANNEL_KEY, suspiciousApps.toEncodedWritableArray(appReactContext)
)

appReactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(THREAT_CHANNEL_NAME, params)
}
}

internal object ThreatListener : FreeraspThreatHandler.TalsecReactNative {
override fun threatDetected(threatType: Threat) {
notifyListeners(threatType)
}

override fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>) {
notifyMalware(suspiciousApps)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ internal object FreeraspThreatHandler : ThreatListener.ThreatDetected, ThreatLis
listener?.threatDetected(Threat.ObfuscationIssues)
}

override fun onMalwareDetected(p0: MutableList<SuspiciousAppInfo>?) {}
override fun onMalwareDetected(suspiciousAppInfos: MutableList<SuspiciousAppInfo>?) {
listener?.malwareDetected(suspiciousAppInfos ?: mutableListOf())
}

override fun onUnlockedDeviceDetected() {
listener?.threatDetected(Threat.Passcode)
Expand All @@ -59,5 +61,7 @@ internal object FreeraspThreatHandler : ThreatListener.ThreatDetected, ThreatLis

internal interface TalsecReactNative {
fun threatDetected(threatType: Threat)

fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>)
}
}
4 changes: 3 additions & 1 deletion android/src/main/java/com/freeraspreactnative/Threat.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal sealed class Threat(val value: Int) {
object ObfuscationIssues : Threat((10000..999999999).random())
object SystemVPN : Threat((10000..999999999).random())
object DevMode : Threat((10000..999999999).random())
object Malware : Threat((10000..999999999).random())

companion object {
internal fun getThreatValues(): WritableArray {
Expand All @@ -39,7 +40,8 @@ internal sealed class Threat(val value: Int) {
DeviceBinding.value,
UnofficialStore.value,
ObfuscationIssues.value,
DevMode.value
DevMode.value,
Malware.value
)
)
}
Expand Down
40 changes: 0 additions & 40 deletions android/src/main/java/com/freeraspreactnative/Utils.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.freeraspreactnative.models

import kotlinx.serialization.Serializable


/**
* Simplified, serializable wrapper for Talsec's SuspiciousAppInfo
*/
@Serializable
data class RNSuspiciousAppInfo(
val packageInfo: RNPackageInfo,
val reason: String,
)

/**
* Simplified, serializable wrapper for Android's PackageInfo
*/
@Serializable
data class RNPackageInfo(
val packageName: String,
val appName: String?,
val version: String?,
val appIcon: String?,
val installerStore: String?
)
111 changes: 111 additions & 0 deletions android/src/main/java/com/freeraspreactnative/utils/Extensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package com.freeraspreactnative.utils

import android.content.pm.PackageInfo
import android.util.Base64
import android.util.Log
import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.freeraspreactnative.exceptions.TalsecException
import com.freeraspreactnative.models.RNPackageInfo
import com.freeraspreactnative.models.RNSuspiciousAppInfo
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json


internal fun ReadableMap.getMapThrowing(key: String): ReadableMap {
return this.getMap(key) ?: throw TalsecException("Key missing in configuration: $key")
}

internal fun ReadableMap.getStringThrowing(key: String): String {
return this.getString(key) ?: throw TalsecException("Key missing in configuration: $key")
}

internal fun ReadableMap.getBooleanSafe(key: String, defaultValue: Boolean = true): Boolean {
if (this.hasKey(key)) {
return this.getBoolean(key)
}
return defaultValue
}

internal fun ReadableArray.toArray(): Array<String> {
val output = mutableListOf<String>()
for (i in 0 until this.size()) {
// in RN versions < 0.63, getString is nullable
@Suppress("UNNECESSARY_SAFE_CALL")
this.getString(i)?.let {
output.add(it)
}
}
return output.toTypedArray()
}

internal fun ReadableMap.getArraySafe(key: String): Array<String> {
if (this.hasKey(key)) {
val inputArray = this.getArray(key)!!
return inputArray.toArray()
}
return arrayOf()
}

internal fun ReadableMap.getNestedArraySafe(key: String): Array<Array<String>> {
val outArray = mutableListOf<Array<String>>()
if (this.hasKey(key)) {
val inputArray = this.getArray(key)!!
for (i in 0 until inputArray.size()) {
outArray.add(inputArray.getArray(i).toArray())
}
}
return outArray.toTypedArray()
}


/**
* Converts the Talsec's SuspiciousAppInfo to React Native equivalent
*/
internal fun SuspiciousAppInfo.toRNSuspiciousAppInfo(context: ReactContext): RNSuspiciousAppInfo {
return RNSuspiciousAppInfo(
packageInfo = this.packageInfo.toRNPackageInfo(context),
reason = this.reason,
)
}

/**
* Converts the Android's PackageInfo to React Native equivalent
*/
internal fun PackageInfo.toRNPackageInfo(context: ReactContext): RNPackageInfo {
return RNPackageInfo(
packageName = this.packageName,
appName = Utils.getAppName(context, this.applicationInfo),
version = this.versionName,
appIcon = Utils.getAppIconAsBase64String(context, this.packageName),
installerStore = Utils.getInstallationSource(context, this.packageName)
)
}

/**
* Convert the Talsec's SuspiciousAppInfo to base64-encoded json array,
* which can be then sent to React Native
*/
internal fun MutableList<SuspiciousAppInfo>.toEncodedWritableArray(context: ReactContext): WritableArray {
val output = Arguments.createArray()
this.forEach { suspiciousAppInfo ->
val rnSuspiciousAppInfo = suspiciousAppInfo.toRNSuspiciousAppInfo(context)
try {
val encodedAppInfo =
Base64.encodeToString(
Json.encodeToString(rnSuspiciousAppInfo).toByteArray(),
Base64.DEFAULT
)
output.pushString(encodedAppInfo)
} catch (e: Exception) {
Log.e("Talsec", "Could not serialize suspicious app data: ${e.message}")
}

}
return output
}

Loading
Loading