fix(tuic): subscription token from Config; formal certs verify
- BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates fix(tuic): subscription token from Config; formal certs verify - BuildShare/Decode: TUIC v4 token only from listener Config, never panel password - listenerToProxies: refuse tuic-v4 without Config token - skip-cert-verify only for panel self-signed (O=3m-ui / ShouldSkipCertVerify), not for operator formal certificates
This commit is contained in:
1 parent
5338c51414
commit
2cd92bd1e7
3 files changed
+60
-17
No files matched your search
@@ -348,10 +348,9 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
p["sni"] = sn
|
||||
}
|
||||
}
|
||||
// Only panel self-signed (or explicit skip) — not operator-provided formal certs.
|
||||
if certutil.ShouldSkipCertVerify(opts) {
|
||||
p["skip-cert-verify"] = true
|
||||
} else if cert, _ := opts["certificate"].(string); strings.TrimSpace(cert) != "" {
|
||||
p["skip-cert-verify"] = true
|
||||
}
|
||||
if value, ok := opts["ech-opts"]; ok {
|
||||
p["ech-opts"] = value
|
||||
@@ -360,7 +359,7 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
}
|
||||
case "tuic", "tuic-v4", "tuic-v5":
|
||||
// MetaCubeX proxies/tuic: type is always "tuic".
|
||||
// V4 = token (string); V5 = uuid + password. Prefer Config token/users.
|
||||
// V4 = token from Config only; V5 = uuid+password.
|
||||
copyTUICOpts := func(p map[string]interface{}) {
|
||||
for _, key := range []string{
|
||||
"congestion-controller", "bbr-profile", "alpn", "max-udp-relay-packet-size",
|
||||
@@ -376,17 +375,26 @@ func listenerToProxies(l models.Listener, server string, credentials []user.Cred
|
||||
p["name-cert-verify"] = v
|
||||
}
|
||||
ensureTUICClientDefaults(p, opts)
|
||||
// Formal cert: keep SNI as public host. Self-signed: skip-verify already set.
|
||||
if p["sni"] == nil && p["servername"] == nil && server != "" {
|
||||
p["sni"] = server
|
||||
}
|
||||
}
|
||||
if tuicTokenNonEmpty(opts["token"]) {
|
||||
// Prefer Config token for any TUIC that still has token (v4 / dual).
|
||||
if tuicTokenNonEmpty(opts["token"]) && protocol != "tuic-v5" {
|
||||
p := makeProxy("")
|
||||
p["token"] = normalizeTUICToken(opts["token"])
|
||||
tok := normalizeTUICToken(opts["token"])
|
||||
if s, ok := tok.(string); !ok || strings.TrimSpace(s) == "" {
|
||||
return nil, fmt.Errorf("listener %q: empty TUIC token in config", l.Name)
|
||||
}
|
||||
p["token"] = tok
|
||||
copyTUICOpts(p)
|
||||
result = append(result, p)
|
||||
break
|
||||
}
|
||||
if protocol == "tuic-v4" {
|
||||
return nil, fmt.Errorf("listener %q: tuic-v4 requires non-empty token in listener config", l.Name)
|
||||
}
|
||||
// V5 users from credentials (already preferred from Config when present).
|
||||
emitted := 0
|
||||
for i, cred := range credentials {
|
||||
@@ -830,16 +838,16 @@ func ensureTUICClientDefaults(p, opts map[string]interface{}) {
|
||||
if p["congestion-controller"] == nil {
|
||||
p["congestion-controller"] = "bbr"
|
||||
}
|
||||
// Panel mints self-signed PEMs (CN=localhost). Public host (domain/IP)
|
||||
// will never match — clients must skip verify or the handshake fails.
|
||||
// Panel self-signed only. Formal certs (Let's Encrypt etc.) must verify.
|
||||
if certutil.ShouldSkipCertVerify(opts) {
|
||||
p["skip-cert-verify"] = true
|
||||
} else if cert, _ := opts["certificate"].(string); strings.TrimSpace(cert) != "" {
|
||||
p["skip-cert-verify"] = true
|
||||
}
|
||||
if p["udp"] == nil {
|
||||
p["udp"] = true
|
||||
}
|
||||
if p["udp-relay-mode"] == nil {
|
||||
p["udp-relay-mode"] = "native"
|
||||
}
|
||||
}
|
||||
|
||||
func copyClientTLS(dst, src map[string]interface{}) {
|
||||
|
||||
@@ -131,14 +131,18 @@ func DecodeNodeModel(l models.Listener, users []UserCred) (NodeModel, error) {
|
||||
n.Users = []UserCred{{Password: key}}
|
||||
}
|
||||
}
|
||||
// TUIC v4: token: [TOKEN] (wiki inbound). Export one share per token.
|
||||
if len(n.Users) == 0 && (n.Protocol == "tuic-v4" || n.Protocol == "tuic") {
|
||||
// TUIC v4: token lives only in Config. Panel user rows must not become the token.
|
||||
if n.Protocol == "tuic-v4" || (n.Protocol == "tuic" && len(stringListFrom(cfg, "token")) > 0) {
|
||||
var toks []UserCred
|
||||
for _, tok := range stringListFrom(cfg, "token") {
|
||||
tok = strings.TrimSpace(tok)
|
||||
if tok != "" {
|
||||
n.Users = append(n.Users, UserCred{Password: tok})
|
||||
toks = append(toks, UserCred{Password: tok})
|
||||
}
|
||||
}
|
||||
if len(toks) > 0 {
|
||||
n.Users = toks
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
|
||||
@@ -624,9 +624,14 @@ func (t TUICCompiler) BuildShare(in ShareInput) (Share, error) {
|
||||
if sni == "" {
|
||||
sni = strings.TrimSpace(in.Node.PublicHost)
|
||||
}
|
||||
skipCert := true
|
||||
skipCert := false
|
||||
if v, ok := cfg["skip-cert-verify"].(bool); ok {
|
||||
skipCert = v
|
||||
} else if cert, _ := cfg["certificate"].(string); strings.Contains(cert, "3m-ui") {
|
||||
// Panel GenerateSelfSigned uses O=3m-ui
|
||||
skipCert = true
|
||||
} else if cert, _ := cfg["certificate"].(string); strings.TrimSpace(cert) != "" && sni == "" {
|
||||
skipCert = true
|
||||
}
|
||||
|
||||
isV4 := kind == "tuic-v4"
|
||||
@@ -672,14 +677,18 @@ func (t TUICCompiler) BuildShare(in ShareInput) (Share, error) {
|
||||
|
||||
var uri string
|
||||
if isV4 {
|
||||
token := strings.TrimSpace(in.User.Password)
|
||||
// Token lives on the listener Config — never the panel user password.
|
||||
token := firstNonEmptyTokenValue(cfg["token"])
|
||||
if token == "" {
|
||||
token = strings.TrimSpace(in.User.Username)
|
||||
token = strings.TrimSpace(in.User.Password)
|
||||
// Only accept User.Password when it came from config decode (no UUID).
|
||||
if strings.TrimSpace(in.User.UUID) != "" || looksLikeUUID(in.User.Username) {
|
||||
token = ""
|
||||
}
|
||||
}
|
||||
if token == "" {
|
||||
return Share{}, fmt.Errorf("tuic-v4 share requires token")
|
||||
return Share{}, fmt.Errorf("tuic-v4 share requires token in listener config")
|
||||
}
|
||||
// Client URI: tuic://TOKEN@host:port?... (V4 has no uuid/password).
|
||||
uri = shareName(
|
||||
shareQuery("tuic://"+url.PathEscape(token)+"@"+netutil.JoinHostPort(host, port), params),
|
||||
in.Node.Name,
|
||||
@@ -847,3 +856,25 @@ func strOr(v, def string) string {
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func firstNonEmptyTokenValue(tok interface{}) string {
|
||||
switch v := tok.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
case []string:
|
||||
for _, s := range v {
|
||||
if s = strings.TrimSpace(s); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range v {
|
||||
if s, ok := item.(string); ok {
|
||||
if s = strings.TrimSpace(s); s != "" {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user