import 'dart:io';
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/asymmetric/rsa.dart';
import 'package:pointycastle/digests/sha256.dart';
import 'package:pointycastle/signers/rsa_signer.dart';
import 'package:asn1lib/asn1lib.dart'; // 用于解析 PEM 公钥
/// 定义签名块的魔术字符串,需要与 Python 脚本中的一致。
const String _kSignatureBlockStartMagic = 'DART_AOT_SIGNATURE_BLOCK_START_V1n';
const String _kSignatureBlockEndMagic = 'nDART_AOT_SIGNATURE_BLOCK_END_V1n';
/// 用于存储解析出的签名数据。
class SignatureData {
final String hashAlgorithm;
final String signatureAlgorithm;
final String originalHashBase64;
final String signatureBase64;
final String publicKeyPem;
SignatureData({
required this.hashAlgorithm,
required this.signatureAlgorithm,
required this.originalHashBase64,
required this.signatureBase64,
required this.publicKeyPem,
});
factory SignatureData.fromJson(Map<String, dynamic> json) {
return SignatureData(
hashAlgorithm: json['hash_algorithm'] as String,
signatureAlgorithm: json['signature_algorithm'] as String,
originalHashBase64: json['original_hash_base64'] as String,
signatureBase64: json['signature_base64'] as String,
publicKeyPem: json['public_key_pem'] as String,
);
}
}
/// 辅助函数:从 PEM 格式字符串加载 RSA 公钥。
RSAPublicKey parseRSAPublicKeyFromPem(String pem) {
final lines = pem.split('n');
final base64String = lines
.where((line) =>
!line.startsWith('-----BEGIN') && !line.startsWith('-----END'))
.join('');
final derBytes = base64.decode(base64String);
final parser = ASN1Parser(derBytes);
final topLevel = parser.nextObject() as ASN1Sequence;
ASN1Sequence publicKeySequence;
if (topLevel.elements.length == 2 && topLevel.elements[0] is ASN1Sequence && topLevel.elements[1] is ASN1BitString) {
// X.509 SubjectPublicKeyInfo
final bitString = topLevel.elements[1] as ASN1BitString;
final spkiParser = ASN1Parser(bitString.stringValue as Uint8List);
publicKeySequence = spkiParser.nextObject() as ASN1Sequence;
} else {
// PKCS#1 RSAPublicKey
publicKeySequence = topLevel;
}
final modulus = publicKeySequence.elements[0] as ASN1Integer;
final exponent = publicKeySequence.elements[1] as ASN1Integer;
return RSAPublicKey(modulus.valueAsBigInt, exponent.valueAsBigInt);
}
class AppIntegrityChecker {
/// 尝试从当前可执行文件末尾解析签名数据块。
///
/// 返回:[SignatureData] 对象,如果解析失败则返回 null。
static Future<SignatureData?> _parseSignatureBlock(String executablePath) async {
final file = File(executablePath);
if (!await file.exists()) {
print('错误: 可执行文件不存在: $executablePath');
return null;
}
// 读取文件的最后一部分,以查找签名块
// 假设签名块不会太大,例如不超过 4KB
const int readBufferSize = 4096;
final fileLength = await file.length();
final startOffset = (fileLength - readBufferSize).clamp(0, fileLength).toInt();
final raf = await file.open(mode: FileMode.read);
await raf.setPosition(startOffset);
final buffer = await raf.read(fileLength - startOffset);
await raf.close();
final bufferString = utf8.decode(buffer, allowMalformed: true);
final startIndex = bufferString.indexOf(_kSignatureBlockStartMagic);
final endIndex = bufferString.indexOf(_kSignatureBlockEndMagic, startIndex != -1 ? startIndex + _kSignatureBlockStartMagic.length : 0);
if (startIndex == -1 || endIndex == -1) {
print('警告: 未找到有效的签名块。');
return null;
}
final jsonStartIndex = startIndex + _kSignatureBlockStartMagic.length;
final jsonString = bufferString.substring(jsonStartIndex, endIndex);
try {
final Map<String, dynamic> jsonMap = json.decode(jsonString);
return SignatureData.fromJson(jsonMap);
} catch (e) {
print('错误: 解析签名块JSON失败: $e');
return null;
}
}
/// 计算可执行文件(排除签名块)的 SHA-256 哈希值。
///
/// [executablePath]:可执行文件路径。
/// [signatureBlockEndOffset]:签名块结束的字节偏移量,用于确定哈希计算的范围。
/// 返回:哈希值的字节数组。
static Future<Uint8List> _calculateExecutableHash(String executablePath, int signatureBlockEndOffset) async {
final file = File(executablePath);
if (!await file.exists()) {
throw FileSystemException('文件不存在', executablePath);
}
final input = file.openRead(0, signatureBlockEndOffset); // 只读取签名块之前的部分
final digest = await sha256.bind(input).first;
return Uint8List.fromList(digest.bytes);
}
/// 执行应用程序的完整性检查。
///
/// 返回:如果完整性验证成功则返回 true,否则返回 false。
static Future<bool> checkIntegrity() async {
final executablePath = Platform.executable;
print('正在检查可执行文件: $executablePath 的完整性...');
final SignatureData? signatureData = await _parseSignatureBlock(executablePath);
if (signatureData == null) {
print('❌ 完整性检查失败: 无法解析签名数据块。');
return false;
}
final fileLength = await File(executablePath).length();
// 计算签名块的起始和结束位置,相对于文件末尾的偏移
final endMagicLength = _kSignatureBlockEndMagic.length;
final startMagicLength = _kSignatureBlockStartMagic.length;
// 假设签名块的JSON内容长度
final jsonContentLength = base64.decode(signatureData.signatureBase64).length + base64.decode(signatureData.originalHashBase64).length + signatureData.publicKeyPem.length + 200; // 粗略估计JSON内容的长度,加上一些冗余
// 签名块的总长度,需要与 Python 脚本保持一致
// 实际计算时,应该精确计算 json.dumps(signature_data) 的长度,但这里我们无法获得原始 Python 脚本的精确输出
// 假设我们已经通过某种方式知道精确的签名块长度,或者从签名块中解析出它的长度
// 简化的方法是:从文件末尾向回搜索 START_MAGIC,然后计算其之前的长度
final fullFileContent = await File(executablePath).readAsBytes();
final String fullFileContentString = utf8.decode(fullFileContent, allowMalformed: true);
final startMagicIndex = fullFileContentString.lastIndexOf(_kSignatureBlockStartMagic);
if (startMagicIndex == -1) {
print('❌ 完整性检查失败: 无法在完整文件内容中找到签名块开始标记。');
return false;
}
// 用于计算哈希的文件部分结束偏移量
final executableContentEndOffset = startMagicIndex;
// 1. 计算当前可执行文件(排除签名块)的哈希值
final Uint8List runtimeHashBytes = await _calculateExecutableHash(executablePath, executableContentEndOffset);
final String runtimeHashHex = runtimeHashBytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
print('运行时计算的哈希值: $runtimeHashHex');
// 2. 从签名数据中提取原始哈希和签名
final Uint8List expectedOriginalHashBytes = base64.decode(signatureData.originalHashBase64);
final Uint8List signatureBytes = base64.decode(signatureData.signatureBase64);
if (runtimeHashBytes.length != expectedOriginalHashBytes.length ||
!_compareByteLists(runtimeHashBytes, expectedOriginalHashBytes)) {
print('❌ 完整性检查失败: 运行时哈希值与签名中记录的原始哈希值不匹配。');
return false;
}
// 3. 验证数字签名
try {
final rsaPublicKey = parseRSAPublicKeyFromPem(signatureData.publicKeyPem);
final signer = RSASigner(SHA256Digest(), '0609608648016503040201'); // PSS padding OID
signer.init(false, PublicKeyParameter<RSAPublicKey>(rsaPublicKey)); // false for verification
final RSASignature rsaSignature = RSASignature(signatureBytes);
final bool isValid = signer.verifySignature(runtimeHashBytes, rsaSignature);
if (isValid) {
print('✅ 签名验证成功!二进制文件完整且来自可信源。');
return true;
} else {
print('❌ 签名验证失败!二进制文件可能已被篡改或来源不可信。');
return false;
}
} catch (e) {
print('❌ 签名验证过程中发生错误: $e');
return false;
}
}
/// 比较两个字节列表是否相同。
static bool _compareByteLists(Uint8List a, Uint8List b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
}