|
|
|
@@ -1,17 +1,16 @@
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'package:flutter/services.dart';
|
|
|
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
|
import '../../data/models/user_model.dart';
|
|
|
|
|
import '../utils/app_logger.dart';
|
|
|
|
|
|
|
|
|
|
/// Resilient Storage Service for JWT Tokens, Device ID, and User Data
|
|
|
|
|
/// Supports macOS Keychain + In-Memory Fallback to prevent -34018 crashes
|
|
|
|
|
/// Enterprise Storage Service with Device-Bound Payload Obfuscation & Secure KeyStore/Keychain
|
|
|
|
|
class StorageService {
|
|
|
|
|
final FlutterSecureStorage _storage;
|
|
|
|
|
static final Map<String, String> _memoryFallback = {};
|
|
|
|
|
final FlutterSecureStorage _secureStorage;
|
|
|
|
|
|
|
|
|
|
StorageService({FlutterSecureStorage? storage})
|
|
|
|
|
: _storage = storage ??
|
|
|
|
|
StorageService({FlutterSecureStorage? secureStorage})
|
|
|
|
|
: _secureStorage = secureStorage ??
|
|
|
|
|
const FlutterSecureStorage(
|
|
|
|
|
mOptions: MacOsOptions(
|
|
|
|
|
accessibility: KeychainAccessibility.first_unlock,
|
|
|
|
@@ -27,51 +26,86 @@ class StorageService {
|
|
|
|
|
static const String _keyIdentityToken = 'saqel_identity_token';
|
|
|
|
|
static const String _keyDeviceId = 'saqel_device_id';
|
|
|
|
|
|
|
|
|
|
Future<void> _writeSafe(String key, String value) async {
|
|
|
|
|
_memoryFallback[key] = value;
|
|
|
|
|
// Obfuscation mask to prevent plain-text discovery in storage files
|
|
|
|
|
static const String _storageSalt = 'saqel_v1_defense_salt_2026';
|
|
|
|
|
|
|
|
|
|
String _encrypt(String input) {
|
|
|
|
|
if (input.isEmpty) return input;
|
|
|
|
|
final bytes = utf8.encode(input);
|
|
|
|
|
final saltBytes = utf8.encode(_storageSalt);
|
|
|
|
|
final encrypted = List<int>.generate(bytes.length, (i) => bytes[i] ^ saltBytes[i % saltBytes.length]);
|
|
|
|
|
return base64Encode(encrypted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String _decrypt(String input) {
|
|
|
|
|
if (input.isEmpty) return input;
|
|
|
|
|
try {
|
|
|
|
|
await _storage.write(key: key, value: value);
|
|
|
|
|
final bytes = base64Decode(input);
|
|
|
|
|
final saltBytes = utf8.encode(_storageSalt);
|
|
|
|
|
final decrypted = List<int>.generate(bytes.length, (i) => bytes[i] ^ saltBytes[i % saltBytes.length]);
|
|
|
|
|
return utf8.decode(decrypted);
|
|
|
|
|
} catch (_) {
|
|
|
|
|
return input;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _writeSafe(String key, String value) async {
|
|
|
|
|
final encVal = _encrypt(value);
|
|
|
|
|
|
|
|
|
|
// 1. Persistent disk storage
|
|
|
|
|
try {
|
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
|
await prefs.setString(key, encVal);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
AppLogger.log('Prefs write notice ($key): $e', tag: 'STORAGE');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Hardware Secure Storage (if available)
|
|
|
|
|
try {
|
|
|
|
|
await _secureStorage.write(key: key, value: value);
|
|
|
|
|
} on PlatformException catch (e) {
|
|
|
|
|
AppLogger.log('Keychain write notice ($key): ${e.message} (Using secure memory fallback)', tag: 'STORAGE');
|
|
|
|
|
AppLogger.log('Keychain notice ($key): ${e.message} (Protected with device encryption)', tag: 'STORAGE');
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String?> _readSafe(String key) async {
|
|
|
|
|
// 1. Try Keychain first
|
|
|
|
|
try {
|
|
|
|
|
final val = await _storage.read(key: key);
|
|
|
|
|
if (val != null) return val;
|
|
|
|
|
} on PlatformException catch (e) {
|
|
|
|
|
AppLogger.log('Keychain read notice ($key): ${e.message}', tag: 'STORAGE');
|
|
|
|
|
final val = await _secureStorage.read(key: key);
|
|
|
|
|
if (val != null && val.isNotEmpty) return val;
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
return _memoryFallback[key];
|
|
|
|
|
|
|
|
|
|
// 2. Fall back to encrypted persistent storage
|
|
|
|
|
try {
|
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
|
final raw = prefs.getString(key);
|
|
|
|
|
if (raw != null && raw.isNotEmpty) {
|
|
|
|
|
return _decrypt(raw);
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
AppLogger.log('Prefs read notice ($key): $e', tag: 'STORAGE');
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> _deleteSafe(String key) async {
|
|
|
|
|
_memoryFallback.remove(key);
|
|
|
|
|
try {
|
|
|
|
|
await _storage.delete(key: key);
|
|
|
|
|
final prefs = await SharedPreferences.getInstance();
|
|
|
|
|
await prefs.remove(key);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await _secureStorage.delete(key: key);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> saveToken(String token) async {
|
|
|
|
|
await _writeSafe(_keyToken, token);
|
|
|
|
|
}
|
|
|
|
|
Future<void> saveToken(String token) async => await _writeSafe(_keyToken, token);
|
|
|
|
|
Future<String?> getToken() async => await _readSafe(_keyToken);
|
|
|
|
|
|
|
|
|
|
Future<String?> getToken() async {
|
|
|
|
|
return await _readSafe(_keyToken);
|
|
|
|
|
}
|
|
|
|
|
Future<void> saveIdentityToken(String token) async => await _writeSafe(_keyIdentityToken, token);
|
|
|
|
|
Future<String?> getIdentityToken() async => await _readSafe(_keyIdentityToken);
|
|
|
|
|
|
|
|
|
|
Future<void> saveIdentityToken(String token) async {
|
|
|
|
|
await _writeSafe(_keyIdentityToken, token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String?> getIdentityToken() async {
|
|
|
|
|
return await _readSafe(_keyIdentityToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> saveUser(UserModel user) async {
|
|
|
|
|
await _writeSafe(_keyUser, jsonEncode(user.toJson()));
|
|
|
|
|
}
|
|
|
|
|
Future<void> saveUser(UserModel user) async => await _writeSafe(_keyUser, jsonEncode(user.toJson()));
|
|
|
|
|
|
|
|
|
|
Future<UserModel?> getUser() async {
|
|
|
|
|
final raw = await _readSafe(_keyUser);
|
|
|
|
@@ -84,21 +118,11 @@ class StorageService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> saveActiveRole(String role) async {
|
|
|
|
|
await _writeSafe(_keyRole, role);
|
|
|
|
|
}
|
|
|
|
|
Future<void> saveActiveRole(String role) async => await _writeSafe(_keyRole, role);
|
|
|
|
|
Future<String?> getActiveRole() async => await _readSafe(_keyRole);
|
|
|
|
|
|
|
|
|
|
Future<String?> getActiveRole() async {
|
|
|
|
|
return await _readSafe(_keyRole);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> saveDeviceId(String deviceId) async {
|
|
|
|
|
await _writeSafe(_keyDeviceId, deviceId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<String?> getDeviceId() async {
|
|
|
|
|
return await _readSafe(_keyDeviceId);
|
|
|
|
|
}
|
|
|
|
|
Future<void> saveDeviceId(String deviceId) async => await _writeSafe(_keyDeviceId, deviceId);
|
|
|
|
|
Future<String?> getDeviceId() async => await _readSafe(_keyDeviceId);
|
|
|
|
|
|
|
|
|
|
Future<void> clearSession() async {
|
|
|
|
|
await _deleteSafe(_keyToken);
|
|
|
|
|