From 7a4fbe19d83cb9de71b2dc37d3190bb1da1f4d0a Mon Sep 17 00:00:00 2001 From: liyi Date: Mon, 2 Dec 2024 15:43:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E8=B0=83=E6=95=B4=E6=96=B0=E7=9A=84?= =?UTF-8?q?=E4=B8=AD=E7=BB=A7=E5=8D=8F=E8=AE=AE=E4=B8=AD=E7=9A=84=E4=B8=8A?= =?UTF-8?q?=E6=8A=A5=E7=AD=BE=E5=90=8D=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../startChart/command/message_command.dart | 25 ++- lib/talk/startChart/entity/scp_message.dart | 74 +++++-- lib/talk/startChart/start_chart_manage.dart | 180 ++++++++++++++---- pubspec.yaml | 2 +- 4 files changed, 221 insertions(+), 60 deletions(-) diff --git a/lib/talk/startChart/command/message_command.dart b/lib/talk/startChart/command/message_command.dart index d05fc0c5..76a272a0 100644 --- a/lib/talk/startChart/command/message_command.dart +++ b/lib/talk/startChart/command/message_command.dart @@ -1,19 +1,24 @@ +import 'package:crc32_checksum/crc32_checksum.dart'; import 'package:star_lock/talk/startChart/constant/message_type_constant.dart'; import 'package:star_lock/talk/startChart/constant/payload_type_constant.dart'; import 'package:star_lock/talk/startChart/constant/protocol_flag_constant.dart'; import 'package:star_lock/talk/startChart/entity/scp_message.dart'; class MessageCommand { + /// 客户端去中继上线命令 - static List goOnlineRelay() { + static List goOnlineRelay({ + required String FromPeerId, + required String ToPeerId, +}) { String serializedBytesString = ScpMessage( ProtocolFlag: ProtocolFlagConstant.scp01, MessageType: MessageTypeConstant.Req, MessageId: 1, SpTotal: 0, SpIndex: 0, - FromPeerId: 'ToPeerId', - ToPeerId: 'ToPeerId', + FromPeerId: FromPeerId, + ToPeerId: ToPeerId, Payload: 'hello', PayloadCRC: 55230, PayloadLength: 5, @@ -46,15 +51,18 @@ class MessageCommand { } // 心跳消息 - static List heartbeatMessage() { + static List heartbeatMessage({ + required String FromPeerId, + required String ToPeerId, + }) { ScpMessage message = ScpMessage( ProtocolFlag: ProtocolFlagConstant.scp01, MessageType: MessageTypeConstant.Req, MessageId: 1, SpTotal: 0, SpIndex: 0, - FromPeerId: 'FromPeerId', - ToPeerId: 'ToPeerId', + FromPeerId: FromPeerId, + ToPeerId: ToPeerId, Payload: 'hello', PayloadCRC: 55230, PayloadLength: 5, @@ -73,4 +81,9 @@ class MessageCommand { } return bytes; } + + static int calculationCrc(payload){ + var checkSumResult = Crc32.calculate(payload); + return checkSumResult; + } } diff --git a/lib/talk/startChart/entity/scp_message.dart b/lib/talk/startChart/entity/scp_message.dart index 113cd974..3fc80678 100644 --- a/lib/talk/startChart/entity/scp_message.dart +++ b/lib/talk/startChart/entity/scp_message.dart @@ -144,85 +144,135 @@ class ScpMessage { return bytesToHexString; } - // 反序列化方法 static ScpMessage deserialize(List bytes) { final message = ScpMessage(); - int offset = 0; // ProtocolFlag (4 bytes) if (bytes.length - offset >= 4) { message.ProtocolFlag = utf8.decode(bytes.sublist(offset, offset + 4)); offset += 4; + } else { + throw FormatException("Invalid ProtocolFlag length"); } // MessageType (1 byte) if (bytes.length - offset >= 1) { message.MessageType = bytes[offset]; offset += 1; + } else { + throw FormatException("Invalid MessageType length"); } // MessageId (2 bytes, little-endian) if (bytes.length - offset >= 2) { message.MessageId = (bytes[offset + 1] << 8) | bytes[offset]; offset += 2; + } else { + throw FormatException("Invalid MessageId length"); } // SpTotal (1 byte) if (bytes.length - offset >= 1) { message.SpTotal = bytes[offset]; offset += 1; + } else { + throw FormatException("Invalid SpTotal length"); } // SpIndex (1 byte) if (bytes.length - offset >= 1) { message.SpIndex = bytes[offset]; offset += 1; + } else { + throw FormatException("Invalid SpIndex length"); } // FromPeerId (字符串,长度固定为44字节) if (bytes.length - offset >= 44) { - message.FromPeerId = utf8.decode(bytes.sublist(offset, offset + 44)); + message.FromPeerId = utf8.decode(bytes.sublist(offset, offset + 44)).trimRight(); offset += 44; + } else { + throw FormatException("Invalid FromPeerId length"); } // ToPeerId (字符串,长度固定为44字节) if (bytes.length - offset >= 44) { - message.ToPeerId = utf8.decode(bytes.sublist(offset, offset + 44)); + message.ToPeerId = utf8.decode(bytes.sublist(offset, offset + 44)).trimRight(); offset += 44; + } else { + throw FormatException("Invalid ToPeerId length"); } // PayloadType (2 bytes, little-endian) if (bytes.length - offset >= 2) { message.PayloadType = (bytes[offset + 1] << 8) | bytes[offset]; offset += 2; + } else { + throw FormatException("Invalid PayloadType length"); } // PayloadCRC (2 bytes, little-endian) if (bytes.length - offset >= 2) { message.PayloadCRC = (bytes[offset + 1] << 8) | bytes[offset]; offset += 2; + } else { + throw FormatException("Invalid PayloadCRC length"); } - // PayloadLength (4 bytes, big-endian) + // PayloadLength (4 bytes, little-endian) if (bytes.length - offset >= 4) { + // 打印PayloadLength对应的4个字节 + print('PayloadLength bytes: ${bytes.sublist(offset, offset + 4)}'); + message.PayloadLength = (bytes[offset] | - (bytes[offset + 1] << 8) | - (bytes[offset + 2] << 16) | - (bytes[offset + 3] << 24)); + (bytes[offset + 1] << 8) | + (bytes[offset + 2] << 16) | + (bytes[offset + 3] << 24)); // 修正为 little-endian offset += 4; + } else { + throw FormatException("Invalid PayloadLength length"); } // Payload (字符串,转换为字节) - if (message.PayloadLength != null && - bytes.length - offset >= message.PayloadLength!) { - message.Payload = - utf8.decode(bytes.sublist(offset, offset + message.PayloadLength!)); + if (message.PayloadLength != null && bytes.length - offset >= message.PayloadLength!) { + message.Payload = utf8.decode(bytes.sublist(offset, offset + message.PayloadLength!)); offset += message.PayloadLength!; + } else { + throw FormatException("Invalid Payload or PayloadLength"); } + // 验证PayloadCRC + // if (message.Payload != null) { + // var crcBytes = List.from(utf8.encode(message.Payload!)); + // var calculatedCrc = _calculateCrc16(crcBytes); + // if (calculatedCrc != message.PayloadCRC) { + // throw FormatException("PayloadCRC verification failed. Expected: ${message.PayloadCRC}, Actual: $calculatedCrc"); + // } + // } + return message; } + // CRC-16 计算函数(示例实现,可能需要根据具体协议调整) + static int _calculateCrc16(List data) { + const poly = 0x8005; + int crc = 0xFFFF; + + for (final b in data) { + crc ^= b << 8; + for (int i = 0; i < 8; i++) { + if ((crc & 0x8000) != 0) { + crc = (crc << 1) ^ poly; + } else { + crc <<= 1; + } + crc &= 0xFFFF; + } + } + + return crc; + } + static String bytesToHex(List bytes) { return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(''); diff --git a/lib/talk/startChart/start_chart_manage.dart b/lib/talk/startChart/start_chart_manage.dart index 9d67903d..1de800a0 100644 --- a/lib/talk/startChart/start_chart_manage.dart +++ b/lib/talk/startChart/start_chart_manage.dart @@ -2,11 +2,17 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; +import 'package:convert/convert.dart'; +import 'package:cryptography/cryptography.dart'; +import 'package:encrypt/encrypt.dart'; +import 'package:fast_rsa/fast_rsa.dart' as fastRsa; +import 'package:get/get.dart'; import 'package:pointycastle/asn1/asn1_parser.dart'; import 'package:pointycastle/asn1/primitives/asn1_integer.dart'; import 'package:pointycastle/asn1/primitives/asn1_sequence.dart'; import 'package:pointycastle/asymmetric/api.dart'; import 'package:pointycastle/digests/sha256.dart'; +import 'package:pointycastle/export.dart' as pc; import 'package:star_lock/app_settings/app_settings.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/network/start_chart_api.dart'; @@ -55,11 +61,11 @@ class StartChartManage { String ToPeerId = ''; // 对端ID String FromPeerId = ''; // 我的ID - bool _isLoginSuccessfulToStartChart = false; // 是否在星图登录成功 + final String echoPeerId = '3phX8Ng2cZHz5NtP8xAf6nYy2z1BYytoejgjoHrWMGhH'; // 星图服务初始化 Future init() async { - // 客户端注册 + // 节点注册 await _clientRegister(); // 中继查询 @@ -70,12 +76,6 @@ class StartChartManage { // 初始化udp服务 await _onlineRelayService(); - - // 发送心跳消息 - _sendHeartbeatMessage(); - - // 发送送上线消息 - await _sendOnlineMessage(); } /// 客户端注册 @@ -85,6 +85,8 @@ class StartChartManage { await _requestStarChartRegisterNode(); await _saveStarChartRegisterNodeToStorage(requestStarChartRegisterNode); _log(text: '获取到星图注册节点信息:$requestStarChartRegisterNode'); + FromPeerId = requestStarChartRegisterNode.peer!.id ?? ''; + ToPeerId = echoPeerId; } // 中继查询 @@ -98,12 +100,11 @@ class StartChartManage { if (relayInfoEntity.relay_list?.length != 0) { final data = relayInfoEntity.relay_list?[0]; - FromPeerId = data?.peerID ?? ''; final parseUdpUrl = _parseUdpUrl(data?.listenAddr ?? ''); remoteHost = parseUdpUrl['host'] ?? ''; remotePort = parseUdpUrl['port'] ?? ''; - _log(text: '中继信息----》${data}'); } + _log(text: '中继信息----》${relayInfoEntity}'); } void closeUdpSocket() { @@ -138,6 +139,7 @@ class StartChartManage { _log(text: '收到消息---> 长度:${dg?.data?.length}, 数据:${dg?.data}'); if (dg?.data != null) { final deserialize = ScpMessage.deserialize(dg!.data); + _log(text: '=============${bytesToHex(dg!.data)}'); _log(text: 'Udp收到结构体数据---》$deserialize'); } } catch (e) { @@ -156,15 +158,19 @@ class StartChartManage { reportInformationData: data, ); if (response.statusCode == 200) { - // TODO 登录成功之后的逻辑 _log(text: '星图登录成功'); + // 发送送上线消息 + await _sendOnlineMessage(); + // 发送心跳消息 + _sendHeartbeatMessage(); } } // 发送上线消息 Future _sendOnlineMessage() async { // 组装上线消息 - final message = MessageCommand.goOnlineRelay(); + final message = MessageCommand.goOnlineRelay( + FromPeerId: FromPeerId, ToPeerId: ToPeerId); await _sendMessage(message: message); } @@ -178,7 +184,10 @@ class StartChartManage { } // 发送心跳包消息 - void _sendHeartbeatMessage() { + void _sendHeartbeatMessage() async { + // 从缓存中获取中继信息 + final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); + final String relayPeerId = relayInfoEntity?.relay_list?[0].peerID ?? ''; if (_heartBeatTimerRunning) { _log(text: '心跳已经开始了. 请勿重复发送心跳包消息'); return; @@ -188,7 +197,10 @@ class StartChartManage { seconds: heartbeatIntervalTime, ), (Timer timer) async { - final List message = MessageCommand.heartbeatMessage(); + final List message = MessageCommand.heartbeatMessage( + FromPeerId: FromPeerId, + ToPeerId: relayPeerId, + ); await _sendMessage(message: message); }, ); @@ -248,15 +260,16 @@ class StartChartManage { // 构造上报信息数据参数 Future _makeReportInformationData() async { - // 获取当前时间戳 - int currentTimestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000; + // 从缓存中获取中继信息 + final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); + // 获取公钥 final publicKey = await getPublicKey(); // 获取私钥 final privateKey = await getPrivateKey(); // 生成签名 final sign = await _generateSign( - currentTimestamp: currentTimestamp, + currentTimestamp: relayInfoEntity!.time ?? 0, privateKeyHex: privateKey, ); @@ -264,8 +277,7 @@ class StartChartManage { final List listenAddrDataList = await _makeListenAddrDataList(); - // 从缓存中获取中继信息 - final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); + // final RelayServiceData relayServiceData = RelayServiceData( name: relayInfoEntity?.relay_list?[0].name ?? '', listen_addr: relayInfoEntity?.relay_list?[0].listenAddr ?? '', @@ -278,7 +290,7 @@ class StartChartManage { public_key: publicKey, listen_addr: listenAddrDataList, relay_service: relayServiceData, - time: currentTimestamp, + time: relayInfoEntity.time ?? 0, sign: sign, ); @@ -289,7 +301,7 @@ class StartChartManage { Future> _makeListenAddrDataList() async { final List listenAddrDataList = []; final List localIp = await _getAllIpAddresses(); - // 从缓存中获取中继信息 + // 从缓存中获取中继信息,取出返回的客户端ip地址 final RelayInfoEntity? relayInfoEntity = await Storage.getRelayInfo(); if (relayInfoEntity != null && relayInfoEntity.client_addr != null) { listenAddrDataList.add( @@ -313,7 +325,7 @@ class StartChartManage { return listenAddrDataList ?? []; } - /// 获取本机所有ip + /// 获取本机所有 IP 地址 Future> _getAllIpAddresses() async { final List ipAddresses = []; try { @@ -324,9 +336,21 @@ class StartChartManage { for (final interface in interfaces) { for (final address in interface.addresses) { - if (address.address.isNotEmpty && - !IpConstant.reportExcludeIp.contains(address.address)) { - ipAddresses.add(address.address); + // 获取原始 IP 地址 + String ipAddress = address.address; + + // 解码 URL 编码的字符串 + ipAddress = Uri.decodeFull(ipAddress); + + // 移除 IPv6 地址中的接口标识符(如果有) + if (ipAddress.contains('%')) { + ipAddress = ipAddress.split('%').first; + } + + // 确保 IP 地址不为空且不在排除列表中 + if (ipAddress.isNotEmpty && + !IpConstant.reportExcludeIp.contains(ipAddress)) { + ipAddresses.add(ipAddress); } } } @@ -376,30 +400,104 @@ class StartChartManage { }) async { String resultSign = ''; try { - // 1. 获取当前时间戳并编码为小端字节序 - Uint8List signData = Uint8List(4); - ByteData.view(signData.buffer) - .setUint32(0, currentTimestamp, Endian.little); + // 1. 将 32 位时间戳以小端字节序编码为二进制数据 + Uint8List signData = encodeTimestampToLittleEndianBytes(currentTimestamp); - // 2. 对时间戳数据计算 SHA-256 哈希值 - Digest hash = sha256.convert(signData); + // 2.将十六进制字符串转换为字节数组 + List privateKeyBytes = hexToBytes(privateKeyHex); - // 3. 使用 RSA 私钥进行签名 (需要提供 RsaPrivateKey) - pc.RSAPrivateKey privateKey = - loadPrivateKey(privateKeyHex); // 需要加载你的 RSA 私钥 - Uint8List signature = rsaSign(privateKey, hash.bytes); - - // 4. 将签名结果转换为十六进制字符串 - String hexSignature = signature - .map((byte) => byte.toRadixString(16).padLeft(2, '0')) - .join(); - resultSign = hexSignature; + // 3.将私钥转换为 PEM 格式 + final pemPrivateKey = + convertToPemPrivateKey(privateKeyBytes, isPKCS8: true); + // 4.签名 + var result = await fastRsa.RSA + .signPKCS1v15Bytes(signData, fastRsa.Hash.SHA256, pemPrivateKey); + resultSign = hex.encode(result); } catch (e) { _log(text: '❌--->上报信息生成签名时出现错误: $e'); + e.printError(); } return resultSign ?? ''; } +// 将 32 位时间戳以小端字节序编码为二进制数据 + Uint8List encodeTimestampToLittleEndianBytes(int timestamp) { + // 创建一个 4 字节的 ByteData 对象 + ByteData byteData = ByteData(4); + + // 将 32 位时间戳写入 ByteData,使用小端字节序 + byteData.setUint32(0, timestamp, Endian.little); + + // 将 ByteData 转换为 Uint8List + Uint8List bytes = byteData.buffer.asUint8List(); + + return bytes; + } + +// 将字节数组转换为 PEM 格式的公钥 + String convertToPemPublicKey(List publicKeyBytes, + {bool isPKCS8 = true}) { + // 将字节数组转换为Base64编码的字符串 + String base64PublicKey = base64Encode(publicKeyBytes); +// 添加PEM格式的头尾标签 + String pemHeader; + String pemFooter; + if (isPKCS8) { + // 添加PEM格式的头尾标签 + pemHeader = "-----BEGIN PUBLIC KEY-----"; + pemFooter = "-----END PUBLIC KEY-----"; + } else { + // 添加PEM格式的头尾标签 + pemHeader = "-----BEGIN RSA PUBLIC KEY-----"; + pemFooter = "-----END RSA PUBLIC KEY-----"; + } + + // 将Base64字符串分行为每行64个字符 + const lineLength = 64; + List lines = []; + for (int i = 0; i < base64PublicKey.length; i += lineLength) { + int end = (i + lineLength < base64PublicKey.length) + ? i + lineLength + : base64PublicKey.length; + lines.add(base64PublicKey.substring(i, end)); + } + + // 组合成完整的PEM格式字符串 + return "$pemHeader\n${lines.join('\n')}\n$pemFooter"; + } + + String convertToPemPrivateKey(List privateKeyBytes, + {bool isPKCS8 = true}) { + // 将字节数组转换为Base64编码的字符串 + String base64PrivateKey = base64Encode(privateKeyBytes); + + // 添加PEM格式的头尾标签 + String pemHeader; + String pemFooter; + + if (isPKCS8) { + pemHeader = "-----BEGIN PRIVATE KEY-----"; + pemFooter = "-----END PRIVATE KEY-----"; + } else { + pemHeader = "-----BEGIN RSA PRIVATE KEY-----"; + pemFooter = "-----END RSA PRIVATE KEY-----"; + } + + // 将Base64字符串分行为每行64个字符 + const lineLength = 64; + List lines = []; // 用于存储每一行 + + for (int i = 0; i < base64PrivateKey.length; i += lineLength) { + int end = (i + lineLength < base64PrivateKey.length) + ? i + lineLength + : base64PrivateKey.length; + lines.add(base64PrivateKey.substring(i, end)); + } + + // 组合成完整的PEM格式字符串 + return "$pemHeader\n${lines.join('\n')}\n$pemFooter"; + } + /// 自定义 PEM 格式的 RSA 私钥解析器 pc.RSAPrivateKey loadPrivateKey(String privateKeyHex) { // 将十六进制字符串转换为字节数组 diff --git a/pubspec.yaml b/pubspec.yaml index 968ca02b..262c8a95 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -250,7 +250,7 @@ dependencies: crc32_checksum: ^0.0.2 cryptography: ^2.7.0 asn1lib: ^1.0.0 - + fast_rsa: ^3.6.6 dependency_overrides: #强制设置google_maps_flutter_ios 为 2.5.2