diff --git a/CHANGELOG.md b/CHANGELOG.md index d7460dd..b49f23e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,60 @@ 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). +## [4.3.0] - 2025-10-31 + +- Android SDK version: 17.0.1 +- iOS SDK version: 6.13.0 + +### React Native + +#### Added + +- Added `killOnBypass` to `TalsecConfig` that configures if the app should be terminated when the threat callbacks are suppressed/hooked by an attacker (Android only) ([Issue 65](https://github.com/talsec/Free-RASP-Android/issues/65)) +- Added API for `timeSpoofing` callback into `ThreatEventActions` (Android only) +- Added API for `unsecureWifi` callback into `ThreatEventActions` (Android only) +- Added API for `allChecksFinished` callback into new `RaspExecutionStateEventActions` object +- Added matched permissions to `SuspiciousAppInfo` object when malware detection reason is `suspiciousPermission` + +#### Fixed + +- Resolved potential collision in threat identifiers + +### Android + +#### Added + +- Added `killOnBypass` method to the `TalsecConfig.Builder` that configures if the app should be terminated when the threat callbacks are suppressed/hooked by an attacker [Issue 65](https://github.com/talsec/Free-RASP-Android/issues/65) +- We are introducing a new capability, detecting whether the device time has been tampered with (`timeSpoofing`) +- We are introducing a new capability, detecting whether the location is being spoofed on the device (`locationSpoofing`) +- We are introducing a new capability, detection of unsecure WiFi (`unecureWifi`) +- Removed deprecated functionality `Pbkdf2Native` and both related native libraries (`libpbkdf2_native.so` and `libpolarssl.so`) +- Added new `RaspExecutionState` which contains `onAllChecksFinished()` method, which is triggered after all checks are completed. +- Added matched permissions to `SuspiciousAppInfo` object when malware detection reason is `suspiciousPermission` +- New option to start Talsec, `Talsec.start()` takes new parameter `TalsecMode` that determines the dispatcher thread of initialization and sync checks (uses background thread by default) +- Capability to check if another app has an option `REQUEST_INSTALL_PACKAGES` enabled in the system settings to malware detection + +#### Fixed + +- ANR issue caused by `registerScreenCaptureCallback()` method on the main thread +- `NullPointerException` when checking key alias in Keystore on Android 7 +- `JaCoCo` issue causing `MethodTooLargeException` during instrumentation +- `DeadApplicationException` when calling `Settings.Global.getInt` or `Settings.Secure.getInt` on invalid context +- `AndroidKeyStore` crashes causing `java.util.concurrent.TimeoutException` when calling `finalize()` method on `Cipher` (GC issues) +- Fixed issue with late initializers and `TalsecMode` coroutines scopes + +#### Changed + +- Shortened the value of threat detection interval +- Refactoring of internal architecture of SDK that newly uses Coroutines to manage threading +- Update of internal dependencies and security libraries + +### iOS + +#### Changed + +- Updated internal dependencies + ## [4.2.4] - 2025-09-17 - iOS SDK version: 6.12.1 @@ -112,7 +166,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added interface for screenshot / screen recording blocking on iOS - Added interface for external ID storage -### Android +### Android #### Added @@ -148,7 +202,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Android SDK requires `kotlinVersion` >= `2.0.0` - Set Java verison to 17 -### Android +### Android #### Changed @@ -200,7 +254,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Raised Android compileSDK level to 35 -#### Fixed +#### Fixed - Compatibility issues with RN New Architecture - Added proguard rules for malware data serialization in release mode on Android @@ -278,7 +332,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Added -- Added configuration fields for malware detection +- Added configuration fields for malware detection ### Android diff --git a/android/build.gradle b/android/build.gradle index 9152571..599f0f2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -99,7 +99,7 @@ dependencies { 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:16.0.4" + implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:17.0.1" } if (isNewArchitectureEnabled()) { diff --git a/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt b/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt index 4752375..d1c5807 100644 --- a/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt +++ b/android/src/main/java/com/freeraspreactnative/FreeraspReactNativeModule.kt @@ -7,6 +7,7 @@ import android.os.Looper 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.TalsecMode import com.aheaditec.talsec_security.security.api.ThreatListener import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.LifecycleEventListener @@ -18,6 +19,9 @@ 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 +import com.freeraspreactnative.events.BaseRaspEvent +import com.freeraspreactnative.events.RaspExecutionStateEvent +import com.freeraspreactnative.events.ThreatEvent import com.freeraspreactnative.utils.Utils import com.freeraspreactnative.utils.getArraySafe import com.freeraspreactnative.utils.getBooleanSafe @@ -29,7 +33,7 @@ import com.freeraspreactnative.utils.toEncodedWritableArray class FreeraspReactNativeModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { - private val listener = ThreatListener(FreeraspThreatHandler, FreeraspThreatHandler) + private val listener = ThreatListener(FreeraspThreatHandler, FreeraspThreatHandler, FreeraspThreatHandler) private val lifecycleListener = object : LifecycleEventListener { override fun onHostResume() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { @@ -67,7 +71,7 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex FreeraspThreatHandler.listener = ThreatListener listener.registerListener(reactContext) runOnUiThread { - Talsec.start(reactContext, config) + Talsec.start(reactContext, config, TalsecMode.BACKGROUND) mainHandler.post { talsecStarted = true // This code must be called only AFTER Talsec.start @@ -90,19 +94,39 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex */ @ReactMethod fun getThreatIdentifiers(promise: Promise) { - promise.resolve(Threat.getThreatValues()) + promise.resolve(ThreatEvent.ALL_EVENTS) } /** - * Method to setup the message passing between native and React Native + * Method to get the random identifiers of callbacks + */ + @ReactMethod + fun getRaspExecutionStateIdentifiers(promise: Promise) { + promise.resolve(RaspExecutionStateEvent.ALL_EVENTS) + } + + /** + * Method to setup the threat message passing between native and React Native * @return list of [THREAT_CHANNEL_NAME, THREAT_CHANNEL_KEY] */ @ReactMethod fun getThreatChannelData(promise: Promise) { val channelData: WritableArray = Arguments.createArray() - channelData.pushString(THREAT_CHANNEL_NAME) - channelData.pushString(THREAT_CHANNEL_KEY) - channelData.pushString(MALWARE_CHANNEL_KEY) + channelData.pushString(ThreatEvent.CHANNEL_NAME) + channelData.pushString(ThreatEvent.CHANNEL_KEY) + channelData.pushString(ThreatEvent.MALWARE_CHANNEL_KEY) + promise.resolve(channelData) + } + + /** + * Method to setup the execution state message passing between native and React Native + * @return list of [THREAT_CHANNEL_NAME, THREAT_CHANNEL_KEY] + */ + @ReactMethod + fun getRaspExecutionStateChannelData(promise: Promise) { + val channelData: WritableArray = Arguments.createArray() + channelData.pushString(RaspExecutionStateEvent.CHANNEL_NAME) + channelData.pushString(RaspExecutionStateEvent.CHANNEL_KEY) promise.resolve(channelData) } @@ -207,8 +231,9 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex val talsecBuilder = TalsecConfig.Builder(packageName, certificateHashes) .watcherMail(config.getString("watcherMail")) - .supportedAlternativeStores(androidConfig.getArraySafe("supportedAlternativeStores")) .prod(config.getBooleanSafe("isProd")) + .killOnBypass(config.getBooleanSafe("killOnBypass", false)) + .supportedAlternativeStores(androidConfig.getArraySafe("supportedAlternativeStores")) if (androidConfig.hasKey("malwareConfig")) { val malwareConfig = androidConfig.getMapThrowing("malwareConfig") @@ -223,12 +248,6 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex companion object { const val NAME = "FreeraspReactNative" - private val THREAT_CHANNEL_NAME = (10000..999999999).random() - .toString() // name of the channel over which threat callbacks are sent - private val THREAT_CHANNEL_KEY = (10000..999999999).random() - .toString() // key of the argument map under which threats are expected - private val MALWARE_CHANNEL_KEY = (10000..999999999).random() - .toString() // key of the argument map under which malware data is expected private val backgroundHandlerThread = HandlerThread("BackgroundThread").apply { start() } private val backgroundHandler = Handler(backgroundHandlerThread.looper) @@ -238,11 +257,11 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex internal var talsecStarted = false - private fun notifyListeners(threat: Threat) { + private fun notifyEvent(event: BaseRaspEvent) { val params = Arguments.createMap() - params.putInt(THREAT_CHANNEL_KEY, threat.value) + params.putInt(event.channelKey, event.value) appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - .emit(THREAT_CHANNEL_NAME, params) + .emit(event.channelName, params) } /** @@ -256,25 +275,29 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex mainHandler.post { val params = Arguments.createMap() - params.putInt(THREAT_CHANNEL_KEY, Threat.Malware.value) + params.putInt(ThreatEvent.CHANNEL_KEY, ThreatEvent.Malware.value) params.putArray( - MALWARE_CHANNEL_KEY, encodedSuspiciousApps + ThreatEvent.MALWARE_CHANNEL_KEY, encodedSuspiciousApps ) appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) - .emit(THREAT_CHANNEL_NAME, params) + .emit(ThreatEvent.CHANNEL_NAME, params) } } } } internal object ThreatListener : FreeraspThreatHandler.TalsecReactNative { - override fun threatDetected(threatType: Threat) { - notifyListeners(threatType) + override fun threatDetected(threatEventType: ThreatEvent) { + notifyEvent(threatEventType) } override fun malwareDetected(suspiciousApps: MutableList) { notifyMalware(suspiciousApps) } + + override fun raspExecutionStateChanged(event: RaspExecutionStateEvent) { + notifyEvent(event) + } } } diff --git a/android/src/main/java/com/freeraspreactnative/FreeraspThreatHandler.kt b/android/src/main/java/com/freeraspreactnative/FreeraspThreatHandler.kt index d8be115..3554800 100644 --- a/android/src/main/java/com/freeraspreactnative/FreeraspThreatHandler.kt +++ b/android/src/main/java/com/freeraspreactnative/FreeraspThreatHandler.kt @@ -2,82 +2,102 @@ package com.freeraspreactnative import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo import com.aheaditec.talsec_security.security.api.ThreatListener +import com.freeraspreactnative.events.RaspExecutionStateEvent +import com.freeraspreactnative.events.ThreatEvent -internal object FreeraspThreatHandler : ThreatListener.ThreatDetected, ThreatListener.DeviceState { +internal object FreeraspThreatHandler : ThreatListener.ThreatDetected, ThreatListener.DeviceState, ThreatListener.RaspExecutionState() { internal var listener: TalsecReactNative? = null override fun onRootDetected() { - listener?.threatDetected(Threat.PrivilegedAccess) + listener?.threatDetected(ThreatEvent.PrivilegedAccess) } override fun onDebuggerDetected() { - listener?.threatDetected(Threat.Debug) + listener?.threatDetected(ThreatEvent.Debug) } override fun onEmulatorDetected() { - listener?.threatDetected(Threat.Simulator) + listener?.threatDetected(ThreatEvent.Simulator) } override fun onTamperDetected() { - listener?.threatDetected(Threat.AppIntegrity) + listener?.threatDetected(ThreatEvent.AppIntegrity) } override fun onUntrustedInstallationSourceDetected() { - listener?.threatDetected(Threat.UnofficialStore) + listener?.threatDetected(ThreatEvent.UnofficialStore) } override fun onHookDetected() { - listener?.threatDetected(Threat.Hooks) + listener?.threatDetected(ThreatEvent.Hooks) } override fun onDeviceBindingDetected() { - listener?.threatDetected(Threat.DeviceBinding) + listener?.threatDetected(ThreatEvent.DeviceBinding) } override fun onObfuscationIssuesDetected() { - listener?.threatDetected(Threat.ObfuscationIssues) + listener?.threatDetected(ThreatEvent.ObfuscationIssues) } - override fun onMalwareDetected(suspiciousAppInfos: MutableList?) { + override fun onMalwareDetected(suspiciousAppInfos: MutableList) { listener?.malwareDetected(suspiciousAppInfos ?: mutableListOf()) } override fun onUnlockedDeviceDetected() { - listener?.threatDetected(Threat.Passcode) + listener?.threatDetected(ThreatEvent.Passcode) } override fun onHardwareBackedKeystoreNotAvailableDetected() { - listener?.threatDetected(Threat.SecureHardwareNotAvailable) + listener?.threatDetected(ThreatEvent.SecureHardwareNotAvailable) } override fun onDeveloperModeDetected() { - listener?.threatDetected(Threat.DevMode) + listener?.threatDetected(ThreatEvent.DevMode) } override fun onADBEnabledDetected() { - listener?.threatDetected(Threat.ADBEnabled) + listener?.threatDetected(ThreatEvent.ADBEnabled) } override fun onSystemVPNDetected() { - listener?.threatDetected(Threat.SystemVPN) + listener?.threatDetected(ThreatEvent.SystemVPN) } override fun onScreenshotDetected() { - listener?.threatDetected(Threat.Screenshot) + listener?.threatDetected(ThreatEvent.Screenshot) } override fun onScreenRecordingDetected() { - listener?.threatDetected(Threat.ScreenRecording) + listener?.threatDetected(ThreatEvent.ScreenRecording) } override fun onMultiInstanceDetected() { - listener?.threatDetected(Threat.MultiInstance) + listener?.threatDetected(ThreatEvent.MultiInstance) + } + + override fun onUnsecureWifiDetected() { + listener?.threatDetected(ThreatEvent.UnsecureWifi) + } + + override fun onTimeSpoofingDetected() { + listener?.threatDetected(ThreatEvent.TimeSpoofing) + } + + override fun onLocationSpoofingDetected() { + listener?.threatDetected(ThreatEvent.LocationSpoofing) + } + + override fun onAllChecksFinished() { + listener?.raspExecutionStateChanged(RaspExecutionStateEvent.AllChecksFinished) } internal interface TalsecReactNative { - fun threatDetected(threatType: Threat) + fun threatDetected(threatEventType: ThreatEvent) fun malwareDetected(suspiciousApps: MutableList) + + fun raspExecutionStateChanged(event: RaspExecutionStateEvent) } } diff --git a/android/src/main/java/com/freeraspreactnative/Threat.kt b/android/src/main/java/com/freeraspreactnative/Threat.kt deleted file mode 100644 index 3cf6325..0000000 --- a/android/src/main/java/com/freeraspreactnative/Threat.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.freeraspreactnative - -import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.WritableArray - -/** - * Sealed class to represent the error codes. - * - * Sealed classes are used because of obfuscation - enums classes are not obfuscated well enough. - * - * @property value integer value of the error code. - */ -internal sealed class Threat(val value: Int) { - object AppIntegrity : Threat((10000..999999999).random()) - object PrivilegedAccess : Threat((10000..999999999).random()) - object Debug : Threat((10000..999999999).random()) - object Hooks : Threat((10000..999999999).random()) - object Passcode : Threat((10000..999999999).random()) - object Simulator : Threat((10000..999999999).random()) - object SecureHardwareNotAvailable : Threat((10000..999999999).random()) - object DeviceBinding : Threat((10000..999999999).random()) - object UnofficialStore : Threat((10000..999999999).random()) - object ObfuscationIssues : Threat((10000..999999999).random()) - object SystemVPN : Threat((10000..999999999).random()) - object DevMode : Threat((10000..999999999).random()) - object Malware : Threat((10000..999999999).random()) - object ADBEnabled : Threat((10000..999999999).random()) - object Screenshot : Threat((10000..999999999).random()) - object ScreenRecording : Threat((10000..999999999).random()) - object MultiInstance : Threat((10000..999999999).random()) - - companion object { - internal fun getThreatValues(): WritableArray { - return Arguments.fromList( - listOf( - AppIntegrity.value, - PrivilegedAccess.value, - Debug.value, - Hooks.value, - Passcode.value, - Simulator.value, - SecureHardwareNotAvailable.value, - SystemVPN.value, - DeviceBinding.value, - UnofficialStore.value, - ObfuscationIssues.value, - DevMode.value, - Malware.value, - ADBEnabled.value, - Screenshot.value, - ScreenRecording.value, - MultiInstance.value - ) - ) - } - } -} diff --git a/android/src/main/java/com/freeraspreactnative/events/BaseRaspEvent.kt b/android/src/main/java/com/freeraspreactnative/events/BaseRaspEvent.kt new file mode 100644 index 0000000..dc42359 --- /dev/null +++ b/android/src/main/java/com/freeraspreactnative/events/BaseRaspEvent.kt @@ -0,0 +1,7 @@ +package com.freeraspreactnative.events + +internal interface BaseRaspEvent { + val value: Int + val channelName: String + val channelKey: String +} diff --git a/android/src/main/java/com/freeraspreactnative/events/RaspExecutionStateEvent.kt b/android/src/main/java/com/freeraspreactnative/events/RaspExecutionStateEvent.kt new file mode 100644 index 0000000..6570931 --- /dev/null +++ b/android/src/main/java/com/freeraspreactnative/events/RaspExecutionStateEvent.kt @@ -0,0 +1,21 @@ +package com.freeraspreactnative.events + +import com.facebook.react.bridge.Arguments +import com.freeraspreactnative.utils.RandomGenerator.generateRandomIdentifiers + +internal sealed class RaspExecutionStateEvent(override val value: Int) : BaseRaspEvent { + override val channelName: String get() = CHANNEL_NAME + override val channelKey: String get() = CHANNEL_KEY + + data object AllChecksFinished : RaspExecutionStateEvent(identifiers[2]) + + companion object Companion { + val identifiers = generateRandomIdentifiers(3) + internal val CHANNEL_NAME = identifiers[0].toString() + internal val CHANNEL_KEY = identifiers[1].toString() + internal val ALL_EVENTS = Arguments.fromList( + listOf( + AllChecksFinished + ).map { it.value }) + } +} diff --git a/android/src/main/java/com/freeraspreactnative/events/ThreatEvent.kt b/android/src/main/java/com/freeraspreactnative/events/ThreatEvent.kt new file mode 100644 index 0000000..225aa7f --- /dev/null +++ b/android/src/main/java/com/freeraspreactnative/events/ThreatEvent.kt @@ -0,0 +1,69 @@ +package com.freeraspreactnative.events + +import com.facebook.react.bridge.Arguments +import com.freeraspreactnative.utils.RandomGenerator.generateRandomIdentifiers + +/** + * Sealed class to represent the error codes. + * + * Sealed classes are used because of obfuscation - enums classes are not obfuscated well enough. + * + * @property value integer value of the error code. + */ +internal sealed class ThreatEvent(override val value: Int) : BaseRaspEvent { + override val channelName: String get() = CHANNEL_NAME + override val channelKey: String get() = CHANNEL_KEY + + data object AppIntegrity : ThreatEvent(identifiers[3]) + data object PrivilegedAccess : ThreatEvent(identifiers[4]) + data object Debug : ThreatEvent(identifiers[5]) + data object Hooks : ThreatEvent(identifiers[6]) + data object Passcode : ThreatEvent(identifiers[7]) + data object Simulator : ThreatEvent(identifiers[8]) + data object SecureHardwareNotAvailable : ThreatEvent(identifiers[9]) + data object DeviceBinding : ThreatEvent(identifiers[10]) + data object UnofficialStore : ThreatEvent(identifiers[11]) + data object ObfuscationIssues : ThreatEvent(identifiers[12]) + data object SystemVPN : ThreatEvent(identifiers[13]) + data object DevMode : ThreatEvent(identifiers[14]) + data object Malware : ThreatEvent(identifiers[15]) + data object ADBEnabled : ThreatEvent(identifiers[16]) + data object Screenshot : ThreatEvent(identifiers[17]) + data object ScreenRecording : ThreatEvent(identifiers[18]) + data object MultiInstance : ThreatEvent(identifiers[19]) + data object TimeSpoofing : ThreatEvent(identifiers[20]) + data object LocationSpoofing : ThreatEvent(identifiers[21]) + data object UnsecureWifi : ThreatEvent(identifiers[22]) + + companion object { + val identifiers = generateRandomIdentifiers(23) + + internal val CHANNEL_NAME = identifiers[0].toString() + internal val CHANNEL_KEY = identifiers[1].toString() + internal val MALWARE_CHANNEL_KEY = identifiers[2].toString() + + internal val ALL_EVENTS = Arguments.fromList( + listOf( + AppIntegrity, + PrivilegedAccess, + Debug, + Hooks, + Passcode, + Simulator, + SecureHardwareNotAvailable, + SystemVPN, + DeviceBinding, + UnofficialStore, + ObfuscationIssues, + DevMode, + Malware, + ADBEnabled, + Screenshot, + ScreenRecording, + MultiInstance, + TimeSpoofing, + LocationSpoofing, + UnsecureWifi + ).map { it.value }) + } +} diff --git a/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt b/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt index 14f30d5..11dc239 100644 --- a/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt +++ b/android/src/main/java/com/freeraspreactnative/models/RNSuspiciousAppInfo.kt @@ -10,6 +10,7 @@ import kotlinx.serialization.Serializable data class RNSuspiciousAppInfo( val packageInfo: RNPackageInfo, val reason: String, + val permissions: Set? ) /** diff --git a/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt b/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt index 5928321..5feac76 100644 --- a/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt +++ b/android/src/main/java/com/freeraspreactnative/utils/Extensions.kt @@ -75,6 +75,7 @@ internal fun SuspiciousAppInfo.toRNSuspiciousAppInfo(context: ReactContext): RNS return RNSuspiciousAppInfo( packageInfo = this.packageInfo.toRNPackageInfo(context), reason = this.reason, + permissions = this.permissions ) } diff --git a/android/src/main/java/com/freeraspreactnative/utils/RandomGenerator.kt b/android/src/main/java/com/freeraspreactnative/utils/RandomGenerator.kt new file mode 100644 index 0000000..d0b21f8 --- /dev/null +++ b/android/src/main/java/com/freeraspreactnative/utils/RandomGenerator.kt @@ -0,0 +1,13 @@ +package com.freeraspreactnative.utils + +internal object RandomGenerator { + private val generatedNumbers = mutableSetOf() + + internal fun generateRandomIdentifiers(length: Int): List { + val previousLength = generatedNumbers.size + while (generatedNumbers.size < previousLength + length) { + generatedNumbers.add((10000..999999999).random()) + } + return generatedNumbers.toList().takeLast(length) + } +} diff --git a/android/src/main/java/com/freeraspreactnative/utils/Utils.kt b/android/src/main/java/com/freeraspreactnative/utils/Utils.kt index e523b64..6d0d954 100644 --- a/android/src/main/java/com/freeraspreactnative/utils/Utils.kt +++ b/android/src/main/java/com/freeraspreactnative/utils/Utils.kt @@ -9,6 +9,7 @@ import android.util.Base64 import android.util.Log import com.facebook.react.bridge.ReactContext import java.io.ByteArrayOutputStream +import androidx.core.graphics.createBitmap internal object Utils { @@ -43,11 +44,7 @@ internal object Utils { } if (drawable.intrinsicWidth > 0 && drawable.intrinsicHeight > 0) { - val bitmap = Bitmap.createBitmap( - drawable.intrinsicWidth, - drawable.intrinsicHeight, - Bitmap.Config.ARGB_8888 - ) + val bitmap = createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight) val canvas = Canvas(bitmap) drawable.setBounds(0, 0, canvas.width, canvas.height) drawable.draw(canvas) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 1a609b9..70fce86 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,9 +1,16 @@ + + + + + + + { const [suspiciousApps, setSuspiciousApps] = React.useState< SuspiciousAppInfo[] >([]); + const [allChecksStatus, setAllChecksStatus] = React.useState< + 'in progress' | 'completed' + >('in progress'); useEffect(() => { (async () => { Platform.OS === 'android' && (await addItemsToMalwareWhitelist()); + await requestLocationPermission(); })(); }, []); @@ -50,6 +54,7 @@ const App = () => { }, watcherMail: 'your_email_address@example.com', isProd: true, + killOnBypass: true, }; const actions = { @@ -216,6 +221,69 @@ const App = () => { ) ); }, + // Android only + timeSpoofing: () => { + setAppChecks((currentState) => + currentState.map((threat) => + threat.name === 'Time Spoofing' + ? { ...threat, status: 'nok' } + : threat + ) + ); + }, + // Android only + locationSpoofing: () => { + setAppChecks((currentState) => + currentState.map((threat) => + threat.name === 'Location Spoofing' + ? { ...threat, status: 'nok' } + : threat + ) + ); + }, + // Android only + unsecureWifi: () => { + setAppChecks((currentState) => + currentState.map((threat) => + threat.name === 'Unsecure Wifi' + ? { ...threat, status: 'nok' } + : threat + ) + ); + }, + }; + + const raspExecutionStateActions = { + // Android & iOS + allChecksFinished: () => { + setAllChecksStatus('completed'); + }, + }; + + const requestLocationPermission = async () => { + if (Platform.OS !== 'android') { + return; + } + try { + const results = await PermissionsAndroid.requestMultiple([ + PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION, + PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION, + ]); + + const fineGranted = + results[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION] === + PermissionsAndroid.RESULTS.GRANTED; + const coarseGranted = + results[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION] === + PermissionsAndroid.RESULTS.GRANTED; + + console.info('Location permissions:', { + fineGranted, + coarseGranted, + }); + } catch (err) { + console.warn('Location permission request error:', err); + } }; const addItemsToMalwareWhitelist = async () => { @@ -237,9 +305,15 @@ const App = () => { ); }; - useFreeRasp(config, actions); + useFreeRasp(config, actions, raspExecutionStateActions); - return ; + return ( + + ); }; export default App; diff --git a/example/src/DemoApp.tsx b/example/src/DemoApp.tsx index 08941be..a86cf10 100644 --- a/example/src/DemoApp.tsx +++ b/example/src/DemoApp.tsx @@ -12,6 +12,7 @@ import { TextInput, View, Modal, + ActivityIndicator, } from 'react-native'; import { Colors } from './styles'; import { MalwareModal } from './MalwareModal'; @@ -33,7 +34,8 @@ export const DemoApp: React.FC<{ status: string; }[]; suspiciousApps: SuspiciousAppInfo[]; -}> = ({ checks, suspiciousApps }) => { + allChecksFinishedStatus: 'in progress' | 'completed'; +}> = ({ checks, suspiciousApps, allChecksFinishedStatus }) => { const [hasScreenCaptureBlocked, setHasScreenCaptureBlocked] = React.useState(false); const [externalIdValue, setExternalIdValue] = React.useState(''); @@ -148,6 +150,45 @@ export const DemoApp: React.FC<{ + RASP Execution State: + + + + All Checks Finished + + {allChecksFinishedStatus === 'completed' ? ( + + ) : ( + + )} + + freeRASP checks: {checks.map((check: any, idx: number) => ( = ({ app }) => { Detection reason: {app.reason} + Granted permissions: + + {app.permissions?.join(', ') ?? 'Not specified'} +