Security changes implemented

haroon_dev
haroon amjad 11 hours ago
parent e93771df33
commit 83b17532d0

@ -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

@ -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;
}

@ -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>(() => PermissionService());
getIt.registerLazySingleton<TurnstileService>(() => TurnstileService(getIt<LoggerService>()));
getIt.registerLazySingleton<SecurityService>(() => SecurityServiceImpl(
appState: getIt(),
loggerService: getIt(),
));
// Repositories
getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt()));

@ -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
);

@ -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<void> _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<void> callAppStateInitializations() async {
final String deviceTypeId = (Platform.isIOS
@ -110,6 +114,10 @@ Future<void> 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<void> 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<SecurityService>().initialize();
}
// Set up critical threat callback to navigate to unsafe device page
getIt.get<SecurityService>().setOnCriticalThreatCallback(() {
final navigationService = getIt.get<NavigationService>();
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<LoggerService>().logError('🚨 Navigated to UnsafeDevice page - Critical threat detected');
}
});
// Initialize App Lifecycle Service to monitor background/foreground transitions
getIt.get<AppLifecycleService>().initialize();

@ -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<void> initialize();
/// Check if device is currently safe
bool get isSafeDevice;
/// Get list of detected threats
List<ThreatEvent> 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<ThreatEvent> _detectedThreats = [];
bool _isInitialized = false;
OnCriticalThreatDetected? _onCriticalThreatCallback;
SecurityServiceImpl({
required this.appState,
required this.loggerService,
});
@override
bool get isSafeDevice => appState.isSafeDevice;
@override
List<ThreatEvent> get detectedThreats => List.unmodifiable(_detectedThreats);
@override
Future<void> 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!();
}
}
}
}

@ -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<SplashPage> {
late AuthenticationViewModel authVm;
bool isJailBroken = false;
bool isRealDevice = true;
bool isDevelopmentModeEnable = false;
Future<void> initializeStuff() async {
listenerEvent();
if (kReleaseMode) {
checkDeviceSafety();
}
Timer(
Duration(milliseconds: 500),
() async {
@ -51,34 +60,43 @@ class _SplashScreenState extends State<SplashPage> {
PushNotificationHandler().init(context); // Asyncronously
},
);
await authVm.getServicePrivilege();
Timer(Duration(seconds: 2, milliseconds: 500), () async {
bool isAppOpenedFromCall = getIt.get<CacheService>().getBool(key: CacheConst.isAppOpenedFromCall) ?? false;
// Initialize NotificationService using dependency injection
final notificationService = getIt.get<NotificationService>();
await notificationService.initialize(onNotificationClick: (payload) {
// Handle notification click here
});
if (isJailBroken || !isRealDevice || !getIt.get<AppState>().isSafeDevice) {
// Critical threat detected - navigate to unsafe device page
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const UnsafeDevice()),
(route) => false, // Remove all previous routes
);
} else {
bool isAppOpenedFromCall = getIt.get<CacheService>().getBool(key: CacheConst.isAppOpenedFromCall) ?? false;
// Initialize NotificationService using dependency injection
final notificationService = getIt.get<NotificationService>();
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<NavigationService>().navigatorKey.currentContext!).pushReplacement(FadePage(page: OnboardingScreen()));
if (isAppOpenedFromCall) {
navigateToTeleConsult();
} else {
// Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation())));
Navigator.of(getIt.get<NavigationService>().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<NavigationService>().navigatorKey.currentContext!).pushReplacement(FadePage(page: OnboardingScreen()));
} else {
// Navigator.of(context).pushReplacement(FadePage(page: SplashAnimationScreen(routeWidget: LandingNavigation())));
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pushReplacement(FadePage(page: LandingNavigation()));
}
}
}
});
// var zoom = ZoomVideoSdk();
// InitConfig initConfig = InitConfig(
// domain: "zoom.us",
@ -154,6 +172,29 @@ class _SplashScreenState extends State<SplashPage> {
);
}
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<void> listenerEvent() async {
print('Call Canceled : ------->');

@ -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<UnsafeDevice> createState() => _UnsafeDeviceState();
}
class _UnsafeDeviceState extends State<UnsafeDevice> {
@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<SecurityService>().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<SecurityService>()
// .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),
// ],
],
),
),
),
);
}
}

@ -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:

Loading…
Cancel
Save