From 83b17532d04acb085740935e985f3cd8272d4332 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 17 Aug 2026 16:19:01 +0300 Subject: [PATCH] Security changes implemented --- .../gradle/wrapper/gradle-wrapper.properties | 3 +- lib/core/app_state.dart | 9 +- lib/core/dependencies.dart | 5 + lib/core/talsec_config.dart | 15 ++ lib/main.dart | 42 ++++- lib/services/security_service.dart | 157 ++++++++++++++++++ lib/splashPage.dart | 79 ++++++--- lib/unsafe_device.dart | 117 +++++++++++++ pubspec.yaml | 2 + 9 files changed, 401 insertions(+), 28 deletions(-) create mode 100644 lib/core/talsec_config.dart create mode 100644 lib/services/security_service.dart create mode 100644 lib/unsafe_device.dart diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index ac3b4792..2299155e 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index a27b727e..0d015922 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -170,7 +170,6 @@ class AppState { set setIsAuthenticated(v) => isAuthenticated = v; - String deviceTypeID = ""; set setDeviceTypeID(v) => deviceTypeID = v; @@ -179,6 +178,14 @@ class AppState { String get getFamilyFileTokenID => _familyFileTokenID; + bool isSafeDevice = true; + + // set setIsSafeDevice(v) => isSafeDevice = v; + + set setIsSafeDevice(bool value) { + isSafeDevice = value; + } + set setFamilyFileTokenID(String value) { _familyFileTokenID = value; } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index e4ca4c25..b6c7fb38 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -84,6 +84,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/notification_service.dart'; import 'package:hmg_patient_app_new/services/permission_service.dart'; +import 'package:hmg_patient_app_new/services/security_service.dart'; import 'package:hmg_patient_app_new/core/services/turnstile_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; @@ -157,6 +158,10 @@ class AppDependencies { getIt.registerLazySingleton(() => PermissionService()); getIt.registerLazySingleton(() => TurnstileService(getIt())); + getIt.registerLazySingleton(() => SecurityServiceImpl( + appState: getIt(), + loggerService: getIt(), + )); // Repositories getIt.registerLazySingleton(() => CommonRepoImp(loggerService: getIt())); diff --git a/lib/core/talsec_config.dart b/lib/core/talsec_config.dart new file mode 100644 index 00000000..1603d419 --- /dev/null +++ b/lib/core/talsec_config.dart @@ -0,0 +1,15 @@ +import 'package:freerasp/freerasp.dart'; + +final talsecConfig = TalsecConfig( + androidConfig: AndroidConfig( + packageName: 'com.cloudsolutions.HMGPatientApp', + signingCertHashes: ['6tvWaoN5coG4SnfxGbdQlcLmM0J4ePQwDjrKIg+QkV0=', 'j6VEqVhrypHMIiXiFdRLDdGwjGaMGWY7KAdBJA+Z4Pc='], // Must be the release cert hash + supportedStores: ['com.android.vending'], // Google Play Store + ), + iosConfig: IOSConfig( + bundleIds: ['com.cloudsolutions.HMGPatientApp'], // iOS Bundle ID + teamId: '3A359E86ZF', // Found in Apple Developer portal + ), + watcherMail: '', // Required to receive security alerts + isProd: true, // Enforces strict checks for release builds +); \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 87746f14..b22bbb6b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -53,11 +53,15 @@ import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/app_lifecycle_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/services/security_service.dart'; import 'package:hmg_patient_app_new/theme/app_theme.dart'; +import 'package:hmg_patient_app_new/unsafe_device.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel; import 'package:provider/provider.dart'; import 'package:provider/single_child_widget.dart'; +import 'package:safe_device/safe_device.dart'; +import 'package:safe_device/safe_device_config.dart'; import 'core/utils/size_utils.dart'; import 'features/monthly_reports/terms_conditions_view_model.dart'; @@ -72,12 +76,12 @@ Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { // flutter3_32 pub run easy_localization:generate -O ./lib/generated -f keys -o locale_keys.g.dart --source-dir ./assets/langs -class MyHttpOverrides extends HttpOverrides { - @override - HttpClient createHttpClient(SecurityContext? context) { - return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true; - } -} +// class MyHttpOverrides extends HttpOverrides { +// @override +// HttpClient createHttpClient(SecurityContext? context) { +// return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true; +// } +// } Future callAppStateInitializations() async { final String deviceTypeId = (Platform.isIOS @@ -110,6 +114,10 @@ Future callInitializations() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); + SafeDevice.init( + SafeDeviceConfig(mockLocationCheckEnabled: false), // disables mock location check on Android + ); + try { // Attempt to get the default app. If it exists, this avoids the error. await Firebase.app(); @@ -122,9 +130,29 @@ Future callInitializations() async { await AppDependencies.addDependencies(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - HttpOverrides.global = MyHttpOverrides(); + // HttpOverrides.global = MyHttpOverrides(); await callAppStateInitializations(); + // Initialize Security Service early to catch threats before app logic runs + if (kReleaseMode) { + await getIt.get().initialize(); + } + + // Set up critical threat callback to navigate to unsafe device page + getIt.get().setOnCriticalThreatCallback(() { + final navigationService = getIt.get(); + final context = navigationService.navigatorKey.currentContext; + + if (context != null) { + // Clear entire navigation stack and show unsafe device page + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute(builder: (_) => const UnsafeDevice()), + (route) => false, // Remove all previous routes + ); + getIt.get().logError('🚨 Navigated to UnsafeDevice page - Critical threat detected'); + } + }); + // Initialize App Lifecycle Service to monitor background/foreground transitions getIt.get().initialize(); diff --git a/lib/services/security_service.dart b/lib/services/security_service.dart new file mode 100644 index 00000000..b72dffeb --- /dev/null +++ b/lib/services/security_service.dart @@ -0,0 +1,157 @@ +import 'package:firebase_crashlytics/firebase_crashlytics.dart'; +import 'package:flutter/foundation.dart'; +import 'package:freerasp/freerasp.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/talsec_config.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +/// Enum to categorize threat severity levels +enum ThreatSeverity { + critical, // Block app usage + warning, // Log only, don't block +} + +/// Model to track threat details +class ThreatEvent { + final String threatType; + final ThreatSeverity severity; + final DateTime timestamp; + final String? additionalInfo; + + ThreatEvent({ + required this.threatType, + required this.severity, + this.additionalInfo, + }) : timestamp = DateTime.now(); +} + +/// Callback type for critical threat detection +typedef OnCriticalThreatDetected = void Function(); + +/// Abstract class defining the security service interface +abstract class SecurityService { + /// Initialize and start the security monitoring + Future initialize(); + + /// Check if device is currently safe + bool get isSafeDevice; + + /// Get list of detected threats + List get detectedThreats; + + /// Set callback for when critical threat is detected + void setOnCriticalThreatCallback(OnCriticalThreatDetected callback); +} + +/// Implementation of SecurityService using Talsec (freeRASP) +class SecurityServiceImpl implements SecurityService { + final AppState appState; + final LoggerService loggerService; + + final List _detectedThreats = []; + bool _isInitialized = false; + OnCriticalThreatDetected? _onCriticalThreatCallback; + + SecurityServiceImpl({ + required this.appState, + required this.loggerService, + }); + + @override + bool get isSafeDevice => appState.isSafeDevice; + + @override + List get detectedThreats => List.unmodifiable(_detectedThreats); + + @override + Future initialize() async { + if (_isInitialized) { + loggerService.logInfo('SecurityService already initialized'); + return; + } + + try { + loggerService.logInfo('Initializing SecurityService with Talsec'); + + // Start the RASP engine + await Talsec.instance.start(talsecConfig); + + // Setup threat callbacks + final callback = ThreatCallback( + onAppIntegrity: () => _handleThreat('App Integrity', ThreatSeverity.critical), + onObfuscationIssues: () => _handleThreat('Obfuscation Issues', ThreatSeverity.warning), + onDebug: () => _handleThreat('Debug Mode', ThreatSeverity.critical), + onDeviceBinding: () => _handleThreat('Device Binding', ThreatSeverity.critical), + onDeviceID: () => _handleThreat('Device ID Mismatch', ThreatSeverity.critical), + onHooks: () => _handleThreat('Hooks Detected', ThreatSeverity.critical), + onPasscode: () => _handleThreat('Passcode Not Set', ThreatSeverity.warning), + onPrivilegedAccess: () => _handleThreat('Privileged Access (Root/Jailbreak)', ThreatSeverity.critical), + onSecureHardwareNotAvailable: () => _handleThreat('Secure Hardware Not Available', ThreatSeverity.warning), + onSimulator: () => _handleThreat('Simulator/Emulator Detected', ThreatSeverity.critical), + onSystemVPN: () => _handleThreat('System VPN Active', ThreatSeverity.warning), + onDevMode: () => _handleThreat('Developer Mode', ThreatSeverity.warning), + onADBEnabled: () => _handleThreat('USB Debugging Enabled', ThreatSeverity.warning), + onUnofficialStore: () => _handleThreat('Unofficial Store Installation', ThreatSeverity.critical), + onScreenshot: () => _handleThreat('Screenshot Detected', ThreatSeverity.warning), + onScreenRecording: () => _handleThreat('Screen Recording Active', ThreatSeverity.warning), + onMultiInstance: () => _handleThreat('Multiple Instances', ThreatSeverity.warning), + onLocationSpoofing: () => _handleThreat('Location Spoofing', ThreatSeverity.warning), + onTimeSpoofing: () => _handleThreat('Time Spoofing', ThreatSeverity.warning), + onAutomation: () => _handleThreat('Automation Detected', ThreatSeverity.warning), + onBootloader: () => _handleThreat('Unlocked Bootloader', ThreatSeverity.critical), + onMalware: (suspiciousApps) => _handleThreat('Malware/Suspicious Apps', ThreatSeverity.critical, additionalInfo: suspiciousApps.toString()), + ); + + Talsec.instance.attachListener(callback); + + _isInitialized = true; + loggerService.logInfo('SecurityService initialized successfully'); + } catch (e) { + loggerService.logError('Failed to initialize SecurityService: $e'); + if (!kDebugMode) { + FirebaseCrashlytics.instance.recordError( + e, + StackTrace.current, + reason: 'SecurityService initialization failed', + fatal: false, + ); + } + rethrow; + } + } + + @override + void setOnCriticalThreatCallback(OnCriticalThreatDetected callback) { + _onCriticalThreatCallback = callback; + loggerService.logInfo('Critical threat callback registered'); + } + + /// Handle detected threats with appropriate severity + void _handleThreat(String threatType, ThreatSeverity severity, {String? additionalInfo}) { + final threat = ThreatEvent( + threatType: threatType, + severity: severity, + additionalInfo: additionalInfo, + ); + + _detectedThreats.add(threat); + + // Log to console + if (severity == ThreatSeverity.critical) { + loggerService.logError('🔴 CRITICAL THREAT: $threatType ${additionalInfo != null ? "- $additionalInfo" : ""}'); + } else { + loggerService.logInfo('⚠️ WARNING: $threatType ${additionalInfo != null ? "- $additionalInfo" : ""}'); + } + + // Block app if critical threat + if (severity == ThreatSeverity.critical) { + appState.setIsSafeDevice = false; + loggerService.logError('Device marked as UNSAFE due to: $threatType'); + + // Trigger callback to navigate to unsafe device page + if (_onCriticalThreatCallback != null) { + _onCriticalThreatCallback!(); + } + } + } +} diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 7407ffec..8b39bf28 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -26,8 +26,10 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/notification_service.dart'; import 'package:hmg_patient_app_new/services/zoom_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/unsafe_device.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:lottie/lottie.dart'; +import 'package:safe_device/safe_device.dart'; import 'core/cache_consts.dart'; import 'core/utils/push_notification_handler.dart'; @@ -42,8 +44,15 @@ class SplashPage extends StatefulWidget { class _SplashScreenState extends State { late AuthenticationViewModel authVm; + bool isJailBroken = false; + bool isRealDevice = true; + bool isDevelopmentModeEnable = false; + Future initializeStuff() async { listenerEvent(); + if (kReleaseMode) { + checkDeviceSafety(); + } Timer( Duration(milliseconds: 500), () async { @@ -51,34 +60,43 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously }, ); + await authVm.getServicePrivilege(); Timer(Duration(seconds: 2, milliseconds: 500), () async { - bool isAppOpenedFromCall = getIt.get().getBool(key: CacheConst.isAppOpenedFromCall) ?? false; - - // Initialize NotificationService using dependency injection - final notificationService = getIt.get(); - await notificationService.initialize(onNotificationClick: (payload) { - // Handle notification click here - }); + if (isJailBroken || !isRealDevice || !getIt.get().isSafeDevice) { + // Critical threat detected - navigate to unsafe device page + Navigator.of(getIt.get().navigatorKey.currentContext!).pushAndRemoveUntil( + MaterialPageRoute(builder: (_) => const UnsafeDevice()), + (route) => false, // Remove all previous routes + ); + } else { + bool isAppOpenedFromCall = getIt.get().getBool(key: CacheConst.isAppOpenedFromCall) ?? false; + // Initialize NotificationService using dependency injection + final notificationService = getIt.get(); + await notificationService.initialize(onNotificationClick: (payload) { + // Handle notification click here + }); - ZoomService().initializeZoomSDK(); + ZoomService().initializeZoomSDK(); - if (!kDebugMode) { - _initializeClarity(); - } + if (!kDebugMode) { + _initializeClarity(); + } - if (isAppOpenedFromCall) { - navigateToTeleConsult(); - } else { - if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { - // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen()))); - Navigator.of(getIt.get().navigatorKey.currentContext!).pushReplacement(FadePage(page: OnboardingScreen())); + if (isAppOpenedFromCall) { + navigateToTeleConsult(); } else { - // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); - Navigator.of(getIt.get().navigatorKey.currentContext!).pushReplacement(FadePage(page: LandingNavigation())); + if (await Utils.getBoolFromPrefs(CacheConst.firstLaunch)) { + // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: OnboardingScreen()))); + Navigator.of(getIt.get().navigatorKey.currentContext!).pushReplacement(FadePage(page: OnboardingScreen())); + } else { + // Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation()))); + Navigator.of(getIt.get().navigatorKey.currentContext!).pushReplacement(FadePage(page: LandingNavigation())); + } } } }); + // var zoom = ZoomVideoSdk(); // InitConfig initConfig = InitConfig( // domain: "zoom.us", @@ -154,6 +172,29 @@ class _SplashScreenState extends State { ); } + void checkDeviceSafety() { + try { + SafeDevice.isJailBroken.then((bool value) { + isJailBroken = value; + }); + SafeDevice.isJailBrokenCustom.then((bool value) { + isJailBroken = value; + }); + SafeDevice.isRealDevice.then((value) { + isRealDevice = value; + }); + + if (Platform.isAndroid) { + // isOnExternalStorage = await SafeDevice.isOnExternalStorage; + // SafeDevice.isDevelopmentModeEnable.then((value) { + // isDevelopmentModeEnable = value; + // }); + } + } catch (error) { + print(error); + } + } + Future listenerEvent() async { print('Call Canceled : ------->'); diff --git a/lib/unsafe_device.dart b/lib/unsafe_device.dart new file mode 100644 index 00000000..4712d70a --- /dev/null +++ b/lib/unsafe_device.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:freerasp/freerasp.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/services/security_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class UnsafeDevice extends StatefulWidget { + const UnsafeDevice({super.key}); + + @override + State createState() => _UnsafeDeviceState(); +} + +class _UnsafeDeviceState extends State { + @override + void initState() { + Talsec.instance.detachListener(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.whiteColor, + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Logo + Utils.buildImgWithAssets(icon: AppAssets.hmgLogo, width: MediaQuery.of(context).size.width * 0.7, height: 90.h, fit: BoxFit.contain), + SizedBox(height: 32.h), + // Warning Icon + Icon( + Icons.security, + size: 80.h, + color: AppColors.primaryRedColor, + ), + SizedBox(height: 24.h), + // Title + Text( + 'Unsafe Device Detected', + style: TextStyle( + fontSize: 24.f, + fontWeight: FontWeight.bold, + color: AppColors.primaryRedColor, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 16.h), + + // Description + Text( + 'For your security, this app cannot run on devices with security vulnerabilities.', + style: TextStyle( + fontSize: 16.f, + color: Colors.black87, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 24.h), + // if (getIt.get().detectedThreats.isNotEmpty) ...[ + // Text( + // 'Detected Issues:', + // style: TextStyle( + // fontSize: 14.f, + // fontWeight: FontWeight.bold, + // color: Colors.black87, + // ), + // ), + // SizedBox(height: 8.h), + // Container( + // padding: EdgeInsets.all(12.w), + // decoration: BoxDecoration( + // color: Colors.red.withOpacity(0.1), + // borderRadius: BorderRadius.circular(8.r), + // border: Border.all(color: Colors.red.withOpacity(0.3)), + // ), + // child: Column( + // children: getIt + // .get() + // .detectedThreats + // .where((t) => t.severity == ThreatSeverity.critical) + // .take(5) // Show max 5 threats + // .map((threat) => Padding( + // padding: EdgeInsets.symmetric(vertical: 4.h), + // child: Row( + // children: [ + // Icon(Icons.error_outline, size: 16.f, color: Colors.red), + // SizedBox(width: 8.w), + // Expanded( + // child: Text( + // threat.threatType, + // style: TextStyle(fontSize: 12.f), + // ), + // ), + // ], + // ), + // )) + // .toList(), + // ), + // ), + // SizedBox(height: 24.h), + // ], + ], + ), + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 4c6b92b3..0c5d78dd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -111,6 +111,8 @@ dependencies: screen_brightness: ^1.0.1 flutter_screenshot_blocker: ^1.0.4 cloudflare_turnstile: ^3.7.2 + freerasp: ^8.2.1 + safe_device: ^1.4.1 dev_dependencies: flutter_test: