Files
mtgodot-poc/audit/contracts/network.security-encoding.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

128 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# network.security_encoding
## Scope
完整核对 40250 客户端的握手时间基准、后续 time sync、sequence 表和 `bSeq` 规则、`_IMPROVED_PACKET_ENCRYPTION_` 的 DH2/CTR、未启用 improved encryption 时的 TEA 安全分支、HybridCrypt/pack 注入、locale codepage、定长字符串以及连接清理。判断标准是调用链、分支条件、状态转移顺序、时钟/资源来源和失败副作用是否一致;“当前端有同名算法”不等于已对齐。
## Reference call chain
- `UserInterface/PythonNetworkStreamPhaseHandShake.cpp`
- `CPythonNetworkStream::HandShakePhase`
- `CPythonNetworkStream::RecvHandshakePacket`
- `CPythonNetworkStream::RecvHandshakeOKPacket`
- `CPythonNetworkStream::RecvHybridCryptKeyPacket`
- `CPythonNetworkStream::RecvHybridCryptSDBPacket`
- `CPythonNetworkStream::RecvKeyAgreementPacket`
- `CPythonNetworkStream::RecvKeyAgreementCompletedPacket`
- `UserInterface/PythonNetworkStreamPhaseLogin.cpp`, `PythonNetworkStreamPhaseSelect.cpp`, `PythonNetworkStreamPhaseGame.cpp`
- 各阶段 `HEADER_GC_HANDSHAKE``HEADER_GC_HANDSHAKE_OK`、HybridCrypt 和 key-agreement allowlist;每个 packet handler 完成后立即从该阶段处理函数返回。
- `UserInterface/AccountConnector.cpp`
- auth connector 的重复 handshake、ping/sequence、legacy security、登录成功后的 Panama key 和 game stream 切换。
- `EterLib/NetStream.cpp`
- `CNetworkStream::SetSecurityMode``IsSecurityMode``SendSequence``__SendInternalBuffer``Clear``Connect``ActivateCipher`
- `EterBase/cipher.cpp`, `cipher.h`
- DH2 Prepare/Agree、算法选择、密钥/IV 派生和 CTR stream 状态。
- `EterBase/tea.cpp`
- 未定义 `_IMPROVED_PACKET_ENCRYPTION_` 时的收发 TEA 缓冲区及 8-byte 对齐。
- `EterLocale/StringCodec.cpp``UserInterface/Locale*.h/.cpp`
- Windows `WideCharToMultiByte`/`MultiByteToWideChar`、CP1258 特殊处理和 locale codepage 来源。
40250 的具体时钟语义:初始 handshake 设置全局 `ELTimer_SetServerMSec(dwTime + lDelta)`,回显 `dwTime + 2*lDelta``lDelta=0`,之后调用 `CTimer::SetBaseTime()`Select/Game 的 handshake 保存 `m_kServerTimeSync`,发送 `HEADER_CG_TIME_SYNC` 并追加 sequence;收到 `HEADER_GC_HANDSHAKE_OK` 后用收到 blank 的本地耗时重新设置全局 server msec。这个全局 owner 和二次校准不能用“本地 stream 能计算一个相对时间”替代。
## Current call chain
- `extension/src/net/classic/classic_stream.cpp/.h`
- `handle_control` 处理 handshake、`HDR_GC_TIME_SYNC`、key agreement/completed、ping、phase`server_frame_ms``steady_clock` 与每个 `ClassicStream` 的两个基准值计算。
- `emit_bytes` 在进入发送队列时立即加密;`flush_send` 只负责写 socket。
- `extension/src/net/classic/classic_cipher.cpp/.h`
- 当前实现包含 40250 DH2/CTR 所需的 RFC 5114 参数、算法 selector、key/IV 派生和双向 CTR 状态。
- `extension/src/net/classic/sequence_table.h`, `wire_classic.h`
- 内置 32768-byte sequence 表和 `is_sequence_cg` 规则。
- `extension/src/net/classic/classic_session.cpp`, `extension/src/net/m2_client.cpp`
- phase/版本发送、time-sync mode 和 Godot 网络生命周期。
- `extension/src/net/secure_cipher.cpp/.h`
- 另一套 libsodium X25519/XChaCha20-Poly1305 backend;它不是 40250 `EterBase::Cipher` 的替代实现,不能作为 classic improved-encryption 的等价证据。
- `extension/src/net/text_codec.h/.cpp`
- classic fixed text now crosses one explicit UTF-8 -> GB2312/GBK wire boundary;
fixed fields are capped after conversion and decoded back to UTF-8.
## Branch matrix
| 场景 | 40250 完整行为 | 当前端实际行为 | 结论 |
|---|---|---|---|
| 初始 handshake | 读取固定包,设置全局 `ELTimer`,回显 `time+2*delta`/`delta=0`,调用 `CTimer::SetBaseTime`auth connector 也有自己的同类路径 | 保存 `m_server_time_base`/`m_client_time_base`,回显字节基本一致;没有全局 `ELTimer`/`CTimer` ownerauth/game 也没有统一时钟服务 | `PARTIAL`:wire 主体接近,时间副作用和 owner 不同 |
| Select/Game 后续 handshake | 保存 `m_kServerTimeSync`,发 `0xFC + handshake + sequence`;收到 blank `HANDSHAKE_OK` 后按 `changeClientTime` 到达时间重新设置全局 server clock | `ClassicStream` 暂存后续 handshake 的 server/client 基准,发 `0xFC`/sequence;收到 blank 后才提交二次校准 | `PARTIAL`:stream 语义已对齐,仍缺少全局 clock owner/跨 connector fixture |
| improved DH2/CTR | `Prepare``Activate`→发送固定 key-agreement reply→收到 completed 后 `ActivateCipher`;completed 前后的缓冲和状态顺序由 `CNetworkStream` 控制 | `ClassicCipher` 的 DH2/selector/派生公式接近 referencestream 也实现 reply/completed/已缓冲密文解密 | `PARTIAL`:算法可做双端 round-trip,但没有 40250 server fixture、固定边界/失败分支和真实分片互操作证据 |
| improved 发送时点 | 明文先进入 send buffer`__SendInternalBuffer` 在真正 flush 时按当前 security mode 加密 | `emit_bytes` 在排队时立即加密,随后再 flush | `PARTIAL`:通常字节顺序相同,但队列、重复 flush、切换 security mode 和部分写时点不是同一语义 |
| legacy TEA | 未定义 improved 时 `SetSecurityMode` 保存独立收发 key;接收按 8-byte 缓冲解密,发送按 8-byte 对齐/缓存后发送;auth/game 均可启用 | classic 侧没有完整的 `SetSecurityMode`/TEA 收发缓冲调用链;`classic_cipher` 中的 TEA 只是可选 DH2 algorithm selector,不是 legacy stream mode | `PARTIAL`:分支没有实现为同一协议路径 |
| HybridCrypt keys/SDB | handshake、login、select、game 阶段均允许动态包;消费固定头+精确 stream 长度,并注入 `CEterPackManager` | header 在 wire table 中可识别,但 classic session/parser 没有等价 pack manager 注入,当前会拒绝或忽略该可选资源分支 | `PARTIAL`:动态 framing 不是完整功能等价 |
| Panama pack/login key | auth 成功按 `loginKey XOR g_adwEncryptKey[0..3]` 解密 pack IV,设置 login key,再连接 game stream | 当前登录/资源主链没有 40250 Panama pack IV 解密和同一 auth→game 交接证据 | `PARTIAL`:资源/密钥副作用缺失或未证明 |
| anti-cheat/HackShield/XTrap | 40250 handshake/game allowlist 有条件编译的 HS/XTrap request/response 分支 | header/尺寸部分存在,但没有 Windows anti-cheat provider 或完整 response 处理 | `PARTIAL`:条件分支未等价 |
| sequence table | `SEQUENCE_TABLE_SIZE=32768``SendSequence` 在 bSeq 开启后逐包取表项、到末尾回 0bSeq 来自 packet registration | 当前内置 32768 表并按 `is_sequence_cg` 添加 trailer;代码注释声称 verbatim,但还没有和 40250 源数组的 hash/逐项生成校验 | `PARTIAL`:主路径存在,证据和注册矩阵未闭合 |
| 版本包 | 根据 locale/region 选择 `CG_CLIENT_VERSION2``CG_CLIENT_VERSION`,固定字段按 Windows locale 字节复制 | 当前已按 `m_locale_is_europe` 选择两个包,字段走 UTF-8 `to_wire` | `PARTIAL`header/分支存在,非 ASCII 字节语义仍不同 |
| locale 转码 | `LocaleService_GetCodePage` + Windows codepage 转换;中文固定字段使用 GB2312-compatible bytes;固定字段本质是 byte array/`strncpy` | classic 使用 Windows CP936 或 POSIX iconv 的 GB2312/GBK 转码;Godot/Native 内部仍保持 UTF-8;其它 locale codepage 尚未接入 | `PARTIAL`:40250 中文固定字段已对齐,非中文 locale 仍不同 |
| fixed string/NUL | reference 对固定 `char[]` 以字节 cap、NUL 结尾和 struct zero-fill 为准;不可表示字符遵循 Win32 codepage 策略 | classic `to_wire()` 在 GB2312 字节上限后截断且结构体 zero-fill;`wire_text_fits()` 按编码后字节拒绝超长/不可表示值;无系统 codec 的平台拒绝高位字节而不泄漏 UTF-8 | `PARTIAL`:中文字段已对齐,fallback/其它 locale 仍需真实 fixture |
| cleanup/failure | `Clear` 清 cipher、socket、TEA buffer、recv/send positions 和 sequencekey agreement/发送失败按阶段返回并由上层断开 | `disconnect` 清 stream/cipher/buffer/sequence;缺少 legacy buffer、global timer、HybridCrypt/pack 和 auth connector 分层清理矩阵 | `PARTIAL` |
## Equivalence matrix
| 项目 | 状态 | 证据 |
|---|---|---|
| Preconditions | `PARTIAL` | classic 对固定长度、DH2 key-ready、completed 顺序有保护;legacy/hybrid/anti-cheat 和阶段 allowlist 不完整 |
| Branch structure | `PARTIAL` | initial/time-sync/improved 主分支存在;TEA、HybridCrypt、Panama、anti-cheat 分支没有完整等价实现 |
| Algorithms/formulas | `PARTIAL` | `ClassicCipher` 的 DH2/CTR 公式与参数接近 reference;缺少 40250 server 密文 fixture,且 legacy TEA stream 没有主链 |
| State transition order | `PARTIAL` | completed 后激活和已有缓冲解密存在;global clock 二次校准、auth/game 双 connector、flush 时加密时点不同 |
| Constants/units | `PARTIAL` | handshake/header/32768 sequence 常量已定位;locale byte cap、TEA block/buffer 和 optional packet size 规则未闭合 |
| Timing/event sources | `PARTIAL` | 当前每个 stream 使用 `steady_clock`,并在 `HANDSHAKE_OK` 提交暂存基准;40250 使用全局 `ELTimer`auth/game 共用时钟 owner 仍不同 |
| Resource/data sources | `PARTIAL` | key/sequence/version 来源已定位;当前没有真实 locale codepage 和 EterPack HybridCrypt/Panama 注入来源 |
| Protocol side effects | `PARTIAL` | handshake/key reply/ping/version 有路径;clock、pack IV、HybridCrypt 和 anti-cheat side effect 缺失或未证明 |
| Interruption/failure/cleanup | `PARTIAL` | classic disconnect 会清基本状态;TEA 对齐缓存、auth connector 和可选资源的失败/重入/断开组合未覆盖 |
## Evidence already available
- `extension/tests/net_classic_cipher_test.cpp`client/server polarity 的 DH2 round-trip、CTR 连续性和新会话密钥差异。
- `extension/tests/net_classic_encstream_test.cpp`handshake、key agreement、completed、已缓冲密文和 sequence 的 stream happy path。
- `extension/tests/net_text_codec_test.cpp`:12 个中文字符在 24 个 GB2312 字节内 round-trip、13 个字符被拒绝、真实中文名称和 32 字节商店招牌边界。
- `extension/tests/net_classic_session_test.cpp`:角色创建/改名包验证实际写入 GB2312 字节、NUL 结尾和 24 字节边界。
- `extension/src/net/classic/sequence_table.h`:32768 项静态表和编译期长度断言;`audit_sequence_table.py` 已逐字节比对 40250 数组,SHA-256 为 `3f6f31964896e712f1f54cb813c7b488b656f700f6ca948ece3313419f876279`
## Deep audit round 2026-09-20
本轮重新检查 `ClassicStream::handle_control/emit_bytes``ClassicCipher`、sequence 表和文本边界,并重跑当前加密/编码回归:
- `net_classic_cipher_test``net_classic_encstream_test``net_text_codec_test` 均退出码 0;它们证明当前 DH2/CTR round-trip、连续 CTR、key agreement/已缓存密文、sequence trailer 和 UTF-8 codepoint-safe 截断没有回归。
- 当前 `HANDSHAKE_OK` 已提交后续 handshake 的暂存 server clock`server_frame_ms()` 仍是每个 `ClassicStream` 的 steady-clock 相对计算,尚未接入 40250 `CTimer/ELTimer` 全局 owner。`net_classic_stream_test` 已覆盖 blank 前不跳时钟、blank 后完成校准。
- 当前 `emit_bytes()` 在进入发送队列时加密,参考端在真正 flush internal buffer 时按当前 security mode 加密;在正常单次发送中可能得到相同字节,但切换 security mode、部分写、重复 flush 和失败清理的状态边界不同。
- `classic_cipher` 中的 TEA 仅作为可选算法 selector,当前 classic stream 没有 40250 legacy `SetSecurityMode` 的独立收发 TEA block buffer 主链;HybridCrypt/SDB、Panama pack IV、HS/XTrap 和其它 locale codepage 也没有完整注入/消费路径。中文 40250 fixed text 已由 `text_codec.cpp` 在网络边界转为 GB2312/GBK-compatible bytes。
- 32768 项 sequence 表已通过 reference 数组逐字节校验;主要 `bSeq` 分支仍依赖当前 wire 注册表,未完成所有版本/locale/可选包的生成式矩阵。
结论:改进加密的局部算法和当前 wire framing 可用,但全局时钟、加密时点、legacy/optional security 分支、codepage 和 sequence 参考数据仍未闭合,合同保持 `PARTIAL`
## Required tests before claiming parity
- 使用 40250 wire fixture 验证初始 handshake、Select/Game handshake、`HANDSHAKE_OK` 二次校准和实际 server-frame 时钟;当前已有 socket-free 二次校准回归。
- 用 40250 server polarity fixture 验证 DH2/CTRsplit recv/send、flush 前后加密、重复 key agreement、completed 前后明文、非法长度、失败清理和 reconnect。
- 保持 40250 sequence 数组逐字节校验,并补齐从 `PacketInfo`/`CMainPacketHeaderMap` 生成的完整 `bSeq` 矩阵。
- 为 CP949/CP1252/CP1258 等非中文部署建立 reference byte fixtures;中文 GB2312 已覆盖不可表示字符、NUL、byte cap、固定字段 zero-fill 和反向 decode。
- 对 HybridCrypt keys/SDB、Panama pack、legacy TEA、HS/XTrap 明确支持/拒绝策略,并分别验证不乱 consume、不把包流错位、不产生错误的成功状态。
## Status
`PARTIAL`
本轮已修复后续 handshake 的二次时钟校准,并加入 40250 sequence table 逐字节审计;DH2/CTR 的局部算法相似性仍不能覆盖全链路差异,在补齐全局 clock、locale、legacy/optional 分支和真实 wire fixture 前,合同保持 `PARTIAL`
## Implementation fix round 2026-09-21 — Chinese fixed-field wire encoding
按 40250 中文客户端的 `LocaleService_GetCodePage`/固定 `char[]` 语义补齐文本边界:Godot 和 native 业务层继续以 UTF-8 保存文本,`text_codec.cpp` 在 classic 发送前按 Windows CP936 或 POSIX GB2312/GBK 转码;`wire_text_fits()` 在转码后按字节容量校验,`to_wire()` 只写完整的 GB2312 字符并依靠 zero-initialized packet 保留 NUL。接收路径用同一 codepage 解回 UTF-8。没有系统 codepage codec 的平台不会把高位 UTF-8 字节直接发送到 40250 字段,而是将该值标记为不可表示。
角色创建、改名以及聊天、密语、公会、商店等 classic 固定/动态文本发送入口均使用这个边界;地图名、任务名和系统提示仍按各自动态包长度处理,不套用 24 字节角色名限制。
回归:`net_text_codec_test``net_classic_session_test``net_classic_stream_test``net_loopback_test`、CTest 23/23、Godot 生命周期/角色选择/移动回归和 `git diff --check` 通过。中文角色名实际创建/改名包已证明 12 个汉字占 24 个 GB2312 字节,13 个汉字被拒绝。合同仍保持 `PARTIAL`,因为 legacy TEA、可选安全分支、其它 locale codepage 和真实服务端中文建号包序仍未完全闭合。
## Implementation fix round 2026-09-21
按 40250 `RecvHandshakePacket`/`RecvHandshakeOKPacket` 修复 `ClassicStream`Select/Game 后续 handshake 只保存 pending server/client 基准,发送 `CG_TIME_SYNC` 后等待一字节 `HANDSHAKE_OK`,再按到达耗时提交 server clock;断开时清理 pending 状态,避免下次连接复用旧校准。新增 `audit_sequence_table.py`,对 40250 `EterLib/NetStream.cpp` 的 32768 项 sequence table 与当前静态表逐字节比较,当前两端 SHA-256 完全一致。
回归:`net_classic_stream_test``net_classic_session_test`、sequence table audit 和 `git diff --check` 通过。全局 `ELTimer/CTimer` owner、legacy TEA、HybridCrypt/Panama/HS-XTrap、Windows codepage、flush 时加密时点和真实 server fixture 仍保持 `PARTIAL`