diff --git a/android/build.gradle b/android/build.gradle
index dbe2ff9..43e7b9b 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -31,7 +31,7 @@ android {
namespace("com.aheaditec.freerasp")
}
- compileSdkVersion 33
+ compileSdkVersion 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index b299bf1..570ec40 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,3 +1,5 @@
+ package="com.aheaditec.freerasp">
+
+
diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/CaptureType.kt b/android/src/main/kotlin/com/aheaditec/freerasp/CaptureType.kt
new file mode 100644
index 0000000..184acac
--- /dev/null
+++ b/android/src/main/kotlin/com/aheaditec/freerasp/CaptureType.kt
@@ -0,0 +1,6 @@
+package com.aheaditec.freerasp
+
+internal sealed class CaptureType(val value: Int) {
+ object Unknown : CaptureType(1115787534)
+
+}
diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt b/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt
index bda94d4..47fe4f3 100644
--- a/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt
+++ b/android/src/main/kotlin/com/aheaditec/freerasp/FreeraspPlugin.kt
@@ -1,6 +1,8 @@
package com.aheaditec.freerasp
+import android.app.Activity
import android.content.Context
+import android.view.WindowManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
@@ -19,6 +21,7 @@ class FreeraspPlugin : FlutterPlugin, ActivityAware, LifecycleEventObserver {
private var methodCallHandler: MethodCallHandler = MethodCallHandler()
private var context: Context? = null
private var lifecycle: Lifecycle? = null
+ private var activity: Activity? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
val messenger = flutterPluginBinding.binaryMessenger
@@ -38,6 +41,8 @@ class FreeraspPlugin : FlutterPlugin, ActivityAware, LifecycleEventObserver {
lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding).also {
it.addObserver(this)
}
+
+ TalsecThreatHandler.guardActivity(binding.getActivity() as Activity?)
}
override fun onDetachedFromActivity() {
diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt b/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt
index ee3a2d0..411e627 100644
--- a/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt
+++ b/android/src/main/kotlin/com/aheaditec/freerasp/Threat.kt
@@ -31,4 +31,6 @@ internal sealed class Threat(val value: Int) {
object SystemVPN : Threat(659382561)
object DevMode : Threat(45291047)
+
+ object ScreenCapture : Threat(669512294)
}
\ No newline at end of file
diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt
index e6d47de..ff79563 100644
--- a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt
+++ b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/MethodCallHandler.kt
@@ -1,6 +1,7 @@
package com.aheaditec.freerasp.handlers
import android.content.Context
+import com.aheaditec.freerasp.CaptureType
import com.aheaditec.freerasp.runResultCatching
import com.aheaditec.freerasp.utils.Utils
import io.flutter.Log
@@ -38,6 +39,7 @@ internal class MethodCallHandler : MethodCallHandler {
it.setMethodCallHandler(this)
}
+ TalsecThreatHandler.attachMethod(sink)
this.context = context
}
@@ -47,6 +49,7 @@ internal class MethodCallHandler : MethodCallHandler {
fun destroyMethodChannel() {
methodChannel?.setMethodCallHandler(null)
methodChannel = null
+ TalsecThreatHandler.detachMethod()
this.context = null
}
@@ -72,11 +75,24 @@ internal class MethodCallHandler : MethodCallHandler {
private fun start(call: MethodCall, result: MethodChannel.Result) {
runResultCatching(result) {
val config = call.argument("config")
- val talsecConfig = Utils.toTalsecConfigThrowing(config)
+ val extendedTalsecConfig = Utils.toExtendedTalsecConfigThrowing(config)
context?.let {
- TalsecThreatHandler.start(it, talsecConfig)
+ TalsecThreatHandler.start(it, extendedTalsecConfig)
} ?: throw IllegalStateException("Unable to run Talsec - context is null")
result.success(null)
}
}
-}
+
+ private val sink = object : MethodSink {
+ override fun onScreenCaptureDetected(captureType: CaptureType) {
+ methodChannel?.invokeMethod(
+ "onScreenCaptureDetected",
+ mapOf(Pair("type", captureType.value))
+ )
+ }
+ }
+
+ internal interface MethodSink {
+ fun onScreenCaptureDetected(captureType: CaptureType)
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt
index 35b381a..9e3d4f1 100644
--- a/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt
+++ b/android/src/main/kotlin/com/aheaditec/freerasp/handlers/TalsecThreatHandler.kt
@@ -1,6 +1,10 @@
package com.aheaditec.freerasp.handlers
+import android.app.Activity
import android.content.Context
+import android.os.Build
+import android.view.WindowManager
+import com.aheaditec.freerasp.CaptureType
import com.aheaditec.freerasp.Threat
import com.aheaditec.talsec_security.security.api.Talsec
import com.aheaditec.talsec_security.security.api.TalsecConfig
@@ -12,16 +16,23 @@ import io.flutter.plugin.common.EventChannel.EventSink
*/
internal object TalsecThreatHandler {
private var eventSink: EventSink? = null
+ private var methodSink: MethodCallHandler.MethodSink? = null
private var isListening = false
+ private var enableScreenCaptureGuard = false
/**
* Initializes [Talsec] and starts the [PluginThreatHandler] listener with this object.
*
* @param context The Android application context.
*/
- internal fun start(context: Context, config: TalsecConfig) {
+ internal fun start(context: Context, extendedConfig: Pair) {
attachListener(context)
- Talsec.start(context, config)
+
+ if (extendedConfig.first.isProd && extendedConfig.second) {
+ enableScreenCaptureGuard = true
+ }
+
+ Talsec.start(context, extendedConfig.first)
}
/**
@@ -116,6 +127,10 @@ internal object TalsecThreatHandler {
flushThreatCache(eventSink)
}
+ internal fun attachMethod(methodSink: MethodCallHandler.MethodSink) {
+ this.methodSink = methodSink
+ }
+
/**
* Called when a listener unsubscribes from the event channel.
*/
@@ -124,6 +139,22 @@ internal object TalsecThreatHandler {
PluginThreatHandler.listener = null
}
+ internal fun detachMethod() {
+ methodSink = null
+ }
+
+ internal fun guardActivity(activity: Activity?) {
+ if (enableScreenCaptureGuard) {
+ activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
+ }
+
+ if (Build.VERSION.SDK_INT >= 34) {
+ Activity.ScreenCaptureCallback {
+ methodSink?.onScreenCaptureDetected(CaptureType.Unknown)
+ }
+ }
+ }
+
/**
* Sends any cached detected threats to the listener.
*
diff --git a/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt b/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt
index e4678ff..27939a7 100644
--- a/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt
+++ b/android/src/main/kotlin/com/aheaditec/freerasp/utils/Utils.kt
@@ -6,7 +6,7 @@ import org.json.JSONObject
internal class Utils {
companion object {
- fun toTalsecConfigThrowing(configJson: String?): TalsecConfig {
+ fun toExtendedTalsecConfigThrowing(configJson: String?): Pair {
if (configJson == null) {
throw JSONException("Configuration is null")
}
@@ -31,12 +31,19 @@ internal class Utils {
isProd = json.getBoolean("isProd")
}
- return TalsecConfig(
- packageName,
- certificateHashes.toTypedArray(),
- watcherMail,
- alternativeStores.toTypedArray(),
- isProd
+ var enableScreenCaptureGuard = false
+ if (json.has("enableScreenCaptureGuard")) {
+ enableScreenCaptureGuard = json.getBoolean("enableScreenCaptureGuard")
+ }
+
+ return Pair(
+ TalsecConfig(
+ packageName,
+ certificateHashes.toTypedArray(),
+ watcherMail,
+ alternativeStores.toTypedArray(),
+ isProd
+ ), enableScreenCaptureGuard
)
}
}
diff --git a/lib/src/models/android_config.dart b/lib/src/models/android_config.dart
index 5a8356e..7eb7092 100644
--- a/lib/src/models/android_config.dart
+++ b/lib/src/models/android_config.dart
@@ -11,6 +11,7 @@ class AndroidConfig {
required this.packageName,
required this.signingCertHashes,
this.supportedStores = const [],
+ this.enableScreenCaptureGuard = false,
}) {
ConfigVerifier.verifyAndroid(this);
}
@@ -30,4 +31,6 @@ class AndroidConfig {
/// List of supported sources where application can be installed from.
final List supportedStores;
+
+ final bool enableScreenCaptureGuard;
}
diff --git a/lib/src/models/android_config.g.dart b/lib/src/models/android_config.g.dart
index 021f8a8..4e4efd1 100644
--- a/lib/src/models/android_config.g.dart
+++ b/lib/src/models/android_config.g.dart
@@ -16,6 +16,8 @@ AndroidConfig _$AndroidConfigFromJson(Map json) =>
?.map((e) => e as String)
.toList() ??
const [],
+ enableScreenCaptureGuard:
+ json['enableScreenCaptureGuard'] as bool? ?? false,
);
Map _$AndroidConfigToJson(AndroidConfig instance) =>
@@ -23,4 +25,5 @@ Map _$AndroidConfigToJson(AndroidConfig instance) =>
'packageName': instance.packageName,
'signingCertHashes': instance.signingCertHashes,
'supportedStores': instance.supportedStores,
+ 'enableScreenCaptureGuard': instance.enableScreenCaptureGuard,
};
diff --git a/lib/src/models/talsec_config.g.dart b/lib/src/models/talsec_config.g.dart
index f901fc5..41aaa56 100644
--- a/lib/src/models/talsec_config.g.dart
+++ b/lib/src/models/talsec_config.g.dart
@@ -12,8 +12,7 @@ TalsecConfig _$TalsecConfigFromJson(Map json) => TalsecConfig(
androidConfig: json['androidConfig'] == null
? null
: AndroidConfig.fromJson(
- json['androidConfig'] as Map,
- ),
+ json['androidConfig'] as Map),
iosConfig: json['iosConfig'] == null
? null
: IOSConfig.fromJson(json['iosConfig'] as Map),
diff --git a/lib/src/talsec.dart b/lib/src/talsec.dart
index 25ad8cf..11a7006 100644
--- a/lib/src/talsec.dart
+++ b/lib/src/talsec.dart
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:convert';
+import 'dart:ffi';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@@ -180,6 +181,14 @@ class Talsec {
break;
}
});
+
+ methodChannel.setMethodCallHandler((call) {
+ if (call.method == "onScreenCaptureDetected") {
+ final value = call.arguments as int;
+
+ callback.onScreenCaptureDetected?.call(CaptureType.fromInt(value));
+ }
+ });
}
/// Removes instance of latest [ThreatCallback]. Also cancels
diff --git a/lib/src/threat_callback.dart b/lib/src/threat_callback.dart
index f91b553..036cace 100644
--- a/lib/src/threat_callback.dart
+++ b/lib/src/threat_callback.dart
@@ -33,6 +33,7 @@ class ThreatCallback {
this.onSecureHardwareNotAvailable,
this.onSystemVPN,
this.onDevMode,
+ this.onScreenCaptureDetected
});
/// This method is called when a threat related dynamic hooking (e.g. Frida)
@@ -80,4 +81,24 @@ class ThreatCallback {
/// This method is called whe the device has Developer mode enabled
final VoidCallback? onDevMode;
+
+ void Function(CaptureType type)? onScreenCaptureDetected;
+}
+
+enum CaptureType {
+ unknown(1115787534),
+ screenshot(0),
+ recording(0),
+ mirroring(0);
+
+ final int value;
+
+ const CaptureType(this.value);
+
+ static CaptureType fromInt(int value) {
+ switch (value) {
+ case 1115787534: return CaptureType.unknown;
+ default: return CaptureType.unknown;
+ }
+ }
}