mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 08:32:52 +08:00
189 lines
8.5 KiB
Lua
189 lines
8.5 KiB
Lua
-- vmess_cipher_test.lua — vmess 加密方式(cipher)与 TLS 层(security)不得混淆
|
||
-- 用法:lua5.1 tests/vmess_cipher_test.lua
|
||
--
|
||
-- 背景:经典 vmess 分享链接 vmess://base64(json) 用 scy 表示**加密方式**
|
||
-- (auto / aes-128-gcm / chacha20-poly1305 / none / zero),用 tls 表示 **TLS 层**
|
||
-- ("tls" 启用,"" 不启用)。而统一模型里 security 一直是 TLS 层
|
||
-- (node.lua DEFAULTS、vless/trojan URI 的 security=、Xray streamSettings.security、
|
||
-- Clash tls: true)。此前解析器把 scy 写进了 security,导致一个不启用 TLS 的节点
|
||
-- 被三个目标同时误判为启用 TLS:
|
||
-- sing-box → "tls":{} (多余且错误)
|
||
-- V2Ray → streamSettings.security: "auto"(非法取值,Xray 拒绝启动)
|
||
-- Clash.Meta→ tls: true (多余且错误)
|
||
-- 本测试锁定修复后的行为。
|
||
|
||
package.path = "./root/usr/share/?.lua;" .. package.path
|
||
|
||
local util = require("substore.util")
|
||
local node = require("substore.node")
|
||
local parser = require("substore.parser")
|
||
local json_parser = require("substore.parser_json_config")
|
||
local clash_parser = require("substore.parser_clash_yaml")
|
||
local output = require("substore.output")
|
||
local uri = require("substore.output_uri")
|
||
|
||
local passed, failed = 0, 0
|
||
|
||
local function check(name, cond)
|
||
if cond then
|
||
passed = passed + 1
|
||
print("PASS " .. name)
|
||
else
|
||
failed = failed + 1
|
||
print("FAIL " .. name)
|
||
end
|
||
end
|
||
|
||
local function vmess_link(j)
|
||
return "vmess://" .. util.base64_encode(util.json_encode(j))
|
||
end
|
||
|
||
-- ---------- 1. 不启用 TLS:scy=auto, tls="" ----------
|
||
-- 这是机场订阅最常见的形态
|
||
local plain = parser.parse_uri(vmess_link({
|
||
v = "2", ps = "PLAIN", add = "1.1.1.1", port = "443",
|
||
id = "11111111-1111-1111-1111-111111111111", aid = "0",
|
||
scy = "auto", net = "tcp", type = "none", host = "", path = "", tls = "",
|
||
}))
|
||
check("plain parses", plain ~= nil and plain.proto == "vmess")
|
||
check("plain security is none (not the cipher)", plain.security == "none")
|
||
check("plain cipher is auto", plain.cipher == "auto")
|
||
check("plain tls normalized to nil", plain.tls == nil)
|
||
|
||
local sb = util.json_decode(output.generate({ plain }, "singbox"))
|
||
check("singbox no spurious tls", sb.outbounds[1].tls == nil)
|
||
check("singbox security is cipher auto", sb.outbounds[1].security == "auto")
|
||
|
||
local v2 = util.json_decode(output.generate({ plain }, "v2ray"))
|
||
check("v2ray no spurious streamSettings.security", v2.outbounds[1].streamSettings.security == nil)
|
||
check("v2ray users security is cipher auto", v2.outbounds[1].settings.vnext[1].users[1].security == "auto")
|
||
|
||
local cm = output.generate({ plain }, "clashmeta")
|
||
check("clashmeta no spurious tls: true", cm:find("tls: true", 1, true) == nil)
|
||
check("clashmeta cipher auto", cm:find("cipher: auto", 1, true) ~= nil)
|
||
|
||
local surge = output.generate({ plain }, "surge")
|
||
check("surge no spurious tls=true", surge:find("tls=true", 1, true) == nil)
|
||
|
||
-- ---------- 2. 启用 TLS + 非默认加密方式 ----------
|
||
local sec = parser.parse_uri(vmess_link({
|
||
v = "2", ps = "SEC", add = "2.2.2.2", port = "443",
|
||
id = "22222222-2222-2222-2222-222222222222", aid = "0",
|
||
scy = "aes-128-gcm", net = "ws", type = "none", host = "", path = "/p",
|
||
tls = "tls", sni = "a.example.com",
|
||
}))
|
||
check("tls security is tls", sec.security == "tls")
|
||
check("tls cipher is aes-128-gcm", sec.cipher == "aes-128-gcm")
|
||
|
||
local sb2 = util.json_decode(output.generate({ sec }, "singbox"))
|
||
check("singbox tls object present", type(sb2.outbounds[1].tls) == "table")
|
||
check("singbox security is the cipher", sb2.outbounds[1].security == "aes-128-gcm")
|
||
|
||
local v22 = util.json_decode(output.generate({ sec }, "v2ray"))
|
||
check("v2ray streamSettings.security is tls", v22.outbounds[1].streamSettings.security == "tls")
|
||
check("v2ray users security is the cipher", v22.outbounds[1].settings.vnext[1].users[1].security == "aes-128-gcm")
|
||
|
||
local cm2 = output.generate({ sec }, "clashmeta")
|
||
check("clashmeta tls: true", cm2:find("tls: true", 1, true) ~= nil)
|
||
check("clashmeta cipher preserved", cm2:find("cipher: aes-128-gcm", 1, true) ~= nil)
|
||
|
||
-- ---------- 3. 分享链接回环:加密方式与 TLS 层都要保住 ----------
|
||
local back = parser.parse_uri(uri.to_share_uri(sec))
|
||
check("roundtrip security tls", back.security == "tls")
|
||
check("roundtrip cipher aes-128-gcm", back.cipher == "aes-128-gcm")
|
||
|
||
local back_plain = parser.parse_uri(uri.to_share_uri(plain))
|
||
check("roundtrip plain security none", back_plain.security == "none")
|
||
check("roundtrip plain cipher auto", back_plain.cipher == "auto")
|
||
|
||
-- ---------- 4. 非法加密方式必须丢弃,而不是写进配置 ----------
|
||
-- 历史脏数据 / 第三方工具可能把 TLS 层写进 scy
|
||
local bad = parser.parse_uri(vmess_link({
|
||
v = "2", ps = "BAD", add = "3.3.3.3", port = "443",
|
||
id = "33333333-3333-3333-3333-333333333333", aid = "0",
|
||
scy = "tls", net = "tcp", tls = "",
|
||
}))
|
||
check("invalid cipher dropped", bad.cipher == nil)
|
||
check("invalid cipher does not become security", bad.security == "none")
|
||
check("invalid cipher falls back to auto on output",
|
||
util.json_decode(output.generate({ bad }, "singbox")).outbounds[1].security == "auto")
|
||
|
||
-- ---------- 5. node.normalize 的 tls 归一化 ----------
|
||
local function norm(t)
|
||
local n = node.normalize({ proto = "vmess", server = "s", port = 1, tls = t })
|
||
return n.tls, n.security
|
||
end
|
||
local t1, s1 = norm("")
|
||
check("normalize tls \"\" -> nil", t1 == nil and s1 == "none")
|
||
local t2, s2 = norm("none")
|
||
check("normalize tls \"none\" -> nil", t2 == nil and s2 == "none")
|
||
local t3, s3 = norm("false")
|
||
check("normalize tls \"false\" -> nil", t3 == nil and s3 == "none")
|
||
local t4, s4 = norm(true)
|
||
check("normalize tls true -> security tls", t4 == true and s4 == "tls")
|
||
local t5, s5 = norm("true")
|
||
check("normalize tls \"true\" (flat yaml) -> security tls", t5 == true and s5 == "tls")
|
||
local t6, s6 = norm("reality")
|
||
check("normalize tls reality -> security reality", t6 == "reality" and s6 == "reality")
|
||
|
||
-- ---------- 6. sing-box JSON 导入:security 是 cipher,TLS 由 tls 对象表达 ----------
|
||
local sb_nodes = json_parser.parse_singbox_json([[{"outbounds":[
|
||
{"tag":"a","type":"vmess","server":"1.1.1.1","server_port":443,"uuid":"u","security":"chacha20-poly1305",
|
||
"tls":{"server_name":"a.example.com"}},
|
||
{"tag":"b","type":"vmess","server":"2.2.2.2","server_port":443,"uuid":"u","security":"auto"}
|
||
]}]])
|
||
check("singbox import count", sb_nodes and #sb_nodes == 2)
|
||
check("singbox import cipher from security", sb_nodes[1].cipher == "chacha20-poly1305")
|
||
check("singbox import tls object -> security", sb_nodes[1].security == "tls")
|
||
check("singbox import sni from tls", sb_nodes[1].sni == "a.example.com")
|
||
check("singbox import no-tls node security none", sb_nodes[2].security == "none")
|
||
check("singbox import no-tls node has no tls", sb_nodes[2].tls == nil)
|
||
check("singbox import roundtrip keeps tls",
|
||
util.json_decode(output.generate({ sb_nodes[1] }, "singbox")).outbounds[1].tls ~= nil)
|
||
|
||
-- ---------- 7. Clash YAML 导入:cipher 是加密方式,tls 是 TLS 层 ----------
|
||
local cy = clash_parser.parse([[
|
||
proxies:
|
||
- name: CY
|
||
type: vmess
|
||
server: 4.4.4.4
|
||
port: 443
|
||
uuid: 44444444-4444-4444-4444-444444444444
|
||
alterId: 0
|
||
cipher: chacha20-poly1305
|
||
tls: true
|
||
network: ws
|
||
ws-opts:
|
||
path: /ws
|
||
]])
|
||
check("clash import count", cy and #cy == 1)
|
||
check("clash import cipher preserved", cy[1].cipher == "chacha20-poly1305")
|
||
check("clash import tls -> security", cy[1].security == "tls")
|
||
local cm3 = output.generate(cy, "clashmeta")
|
||
check("clash->clashmeta cipher preserved", cm3:find("cipher: chacha20-poly1305", 1, true) ~= nil)
|
||
check("clash->clashmeta tls: true", cm3:find("tls: true", 1, true) ~= nil)
|
||
|
||
-- ---------- 8. 简易 YAML 回退路径同样不得混淆 ----------
|
||
-- parse_yaml_content 把 tls: true 读成字符串 "true"
|
||
local flat = output.generate(parser.parse([[
|
||
proxies:
|
||
- name: FLAT
|
||
type: vmess
|
||
server: 5.5.5.5
|
||
port: 443
|
||
uuid: 55555555-5555-5555-5555-555555555555
|
||
cipher: auto
|
||
tls: ""
|
||
]]).nodes, "v2ray")
|
||
local flatd = util.json_decode(flat)
|
||
check("flat yaml no invalid streamSettings.security", flatd.outbounds[1].streamSettings.security == nil)
|
||
|
||
-- ---------- 9. 非 vmess 协议不受影响 ----------
|
||
local tro = node.normalize({ proto = "trojan", server = "6.6.6.6", port = 443, password = "p" })
|
||
check("trojan default security is tls", tro.security == "tls")
|
||
local vless = node.normalize({ proto = "vless", server = "7.7.7.7", port = 443, uuid = "u" })
|
||
check("vless default security is none", vless.security == "none")
|
||
|
||
print(string.format("\n%d passed, %d failed", passed, failed))
|
||
os.exit(failed == 0 and 0 or 1)
|