Files
op-packages/luci-app-substore/tests/vmess_cipher_test.lua
T

189 lines
8.5 KiB
Lua
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.
-- 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)