133 lines
4.6 KiB
Dart
133 lines
4.6 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:crypto/crypto.dart';
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import '../constants/app_constants.dart';
|
|
|
|
class FingerprintService {
|
|
final FlutterSecureStorage _storage;
|
|
final DeviceInfoPlugin _deviceInfo;
|
|
static const MethodChannel _nativeChannel = MethodChannel('iq.urukprize.uruk_prize/device_fingerprint');
|
|
|
|
FingerprintService({
|
|
FlutterSecureStorage? storage,
|
|
DeviceInfoPlugin? deviceInfo,
|
|
}) : _storage = storage ?? const FlutterSecureStorage(),
|
|
_deviceInfo = deviceInfo ?? DeviceInfoPlugin();
|
|
|
|
String? _cachedFingerprint;
|
|
|
|
/// Retrieves or generates a tamper-proof hardware-bound device fingerprint.
|
|
/// Prioritizes Native MethodChannel (Kotlin on Android / Swift on iOS with Keychain persistence).
|
|
Future<String> getDeviceFingerprint() async {
|
|
if (_cachedFingerprint != null) {
|
|
return _cachedFingerprint!;
|
|
}
|
|
|
|
// 1. Check if previously generated and stored securely
|
|
String? storedFp = await _storage.read(key: AppConstants.keyFingerprint);
|
|
if (storedFp != null && storedFp.length == 64) {
|
|
_cachedFingerprint = storedFp;
|
|
return storedFp;
|
|
}
|
|
|
|
// 2. Fetch or create persistent random cryptographic salt
|
|
String? salt = await _storage.read(key: AppConstants.keySalt);
|
|
if (salt == null) {
|
|
salt = const Uuid().v4() + DateTime.now().microsecondsSinceEpoch.toString();
|
|
await _storage.write(key: AppConstants.keySalt, value: salt);
|
|
}
|
|
|
|
// 3. Extract hardware parameters via Native MethodChannel first
|
|
String hardwareData = '';
|
|
try {
|
|
final String? nativeRaw = await _nativeChannel.invokeMethod<String>('getHardwareRawData');
|
|
if (nativeRaw != null && nativeRaw.isNotEmpty) {
|
|
hardwareData = nativeRaw;
|
|
}
|
|
} catch (_) {
|
|
// MethodChannel fallback to device_info_plus
|
|
}
|
|
|
|
if (hardwareData.isEmpty) {
|
|
try {
|
|
if (Platform.isAndroid) {
|
|
final androidInfo = await _deviceInfo.androidInfo;
|
|
hardwareData = '${androidInfo.brand}-${androidInfo.model}-${androidInfo.id}-${androidInfo.hardware}-${androidInfo.manufacturer}';
|
|
} else if (Platform.isIOS) {
|
|
final iosInfo = await _deviceInfo.iosInfo;
|
|
hardwareData = '${iosInfo.name}-${iosInfo.model}-${iosInfo.systemName}-${iosInfo.identifierForVendor}';
|
|
} else {
|
|
hardwareData = 'generic-client-uruk';
|
|
}
|
|
} catch (_) {
|
|
hardwareData = 'fallback-hardware-id';
|
|
}
|
|
}
|
|
|
|
// 4. Generate SHA-256 Digest
|
|
final combinedBytes = utf8.encode('$hardwareData::$salt::uruk_prize_secure');
|
|
final digest = sha256.convert(combinedBytes);
|
|
final fingerprint = digest.toString();
|
|
|
|
// 5. Store permanently
|
|
await _storage.write(key: AppConstants.keyFingerprint, value: fingerprint);
|
|
_cachedFingerprint = fingerprint;
|
|
|
|
return fingerprint;
|
|
}
|
|
|
|
Future<String> getDeviceModel() async {
|
|
try {
|
|
final String? nativeModel = await _nativeChannel.invokeMethod<String>('getDeviceModel');
|
|
if (nativeModel != null && nativeModel.isNotEmpty) {
|
|
return nativeModel;
|
|
}
|
|
} catch (_) {}
|
|
|
|
try {
|
|
if (Platform.isAndroid) {
|
|
final info = await _deviceInfo.androidInfo;
|
|
return '${info.manufacturer} ${info.model}';
|
|
} else if (Platform.isIOS) {
|
|
final info = await _deviceInfo.iosInfo;
|
|
return info.utsname.machine;
|
|
}
|
|
} catch (_) {}
|
|
return 'Unknown Device';
|
|
}
|
|
|
|
Future<void> saveAuthSession({
|
|
required int userId,
|
|
required String token,
|
|
required String deviceSecret,
|
|
}) async {
|
|
await _storage.write(key: AppConstants.keyUserId, value: userId.toString());
|
|
await _storage.write(key: AppConstants.keyToken, value: token);
|
|
await _storage.write(key: AppConstants.keyDeviceSecret, value: deviceSecret);
|
|
}
|
|
|
|
Future<int?> getUserId() async {
|
|
final val = await _storage.read(key: AppConstants.keyUserId);
|
|
return val != null ? int.tryParse(val) : null;
|
|
}
|
|
|
|
Future<String?> getSessionToken() async {
|
|
return await _storage.read(key: AppConstants.keyToken);
|
|
}
|
|
|
|
Future<String?> getDeviceSecret() async {
|
|
return await _storage.read(key: AppConstants.keyDeviceSecret);
|
|
}
|
|
|
|
Future<void> clearSession() async {
|
|
await _storage.delete(key: AppConstants.keyUserId);
|
|
await _storage.delete(key: AppConstants.keyToken);
|
|
await _storage.delete(key: AppConstants.keyDeviceSecret);
|
|
await _storage.delete(key: AppConstants.keyUserData);
|
|
}
|
|
}
|