mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-16 17:46:08 +00:00
82073c10c9
The Clash stream builder computed tlsSettings["pin-sha256"] from the inbound's pinnedPeerCertSha256, but applySecurity's tls case never copied it onto the proxy, so it was written with no reader and silently dropped. Clash subscribers lost certificate pinning while JSON subscribers kept it. Surface pin-sha256 on the proxy in the tls case, matching the JSON emitter.
103 lines
3.8 KiB
Go
103 lines
3.8 KiB
Go
package sub
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
// A subscription is built by iterating every client's share link with no
|
|
// recover(), so any panic in the link generators 500s the whole subscription
|
|
// for every client. Valid-but-unusual stream settings (an empty Reality
|
|
// shortIds/serverNames array, a tcp-http header with no request, a grpc block
|
|
// missing its keys) must therefore produce a link, not a panic.
|
|
func TestGetSubsToleratesUnusualStreamSettings(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
cases := []struct {
|
|
name string
|
|
stream string
|
|
}{
|
|
{"reality empty arrays", `{"network":"tcp","security":"reality","realitySettings":{"serverNames":[],"shortIds":[],"settings":{"publicKey":"pk"}}}`},
|
|
{"tcp http missing request", `{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"http","response":{"headers":{}}}}}`},
|
|
{"tcp http empty path", `{"network":"tcp","security":"none","tcpSettings":{"header":{"type":"http","request":{"path":[]}}}}`},
|
|
{"grpc missing keys", `{"network":"grpc","security":"none","grpcSettings":{}}`},
|
|
{"empty stream settings", `{}`},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
seedSubDB(t)
|
|
subId := fmt.Sprintf("s%d", i)
|
|
seedSubInbound(t, subId, fmt.Sprintf("t%d", i), 46000+i, 1, tc.stream)
|
|
|
|
links, _, _, _, err := NewSubService("").GetSubs(subId, "req.example.com")
|
|
if err != nil {
|
|
t.Fatalf("GetSubs errored: %v", err)
|
|
}
|
|
if len(links) != 1 {
|
|
t.Fatalf("expected 1 share link, got %d", len(links))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The JSON subscription generator for a Hysteria inbound whose StreamSettings
|
|
// omit the hysteriaSettings key must not panic (which would 500 the whole JSON
|
|
// subscription); the raw generator already tolerates this shape.
|
|
func TestGetJsonToleratesHysteriaWithoutHysteriaSettings(t *testing.T) {
|
|
seedSubDB(t)
|
|
db := database.GetDB()
|
|
|
|
const subId = "hy1"
|
|
const email = "hy@e"
|
|
ib := &model.Inbound{
|
|
UserId: 1, Tag: "hy", Enable: true, Listen: "203.0.113.5", Port: 46200,
|
|
Protocol: model.Hysteria,
|
|
Remark: "hy",
|
|
Settings: fmt.Sprintf(`{"version":2,"clients":[{"auth":"hyauth","email":%q,"subId":%q,"enable":true}]}`, email, subId),
|
|
StreamSettings: `{"security":"tls","tlsSettings":{"serverName":"hy.sni"}}`,
|
|
}
|
|
if err := db.Create(ib).Error; err != nil {
|
|
t.Fatalf("seed inbound: %v", err)
|
|
}
|
|
client := &model.ClientRecord{Email: email, SubID: subId, Enable: true}
|
|
if err := db.Create(client).Error; err != nil {
|
|
t.Fatalf("seed client: %v", err)
|
|
}
|
|
if err := db.Create(&model.ClientInbound{ClientId: client.Id, InboundId: ib.Id}).Error; err != nil {
|
|
t.Fatalf("seed client_inbound: %v", err)
|
|
}
|
|
|
|
jsonService := NewSubJsonService("", "", "", NewSubService(""))
|
|
out, _, err := jsonService.GetJson(subId, "sub.example.com", true)
|
|
if err != nil {
|
|
t.Fatalf("GetJson: %v", err)
|
|
}
|
|
if out == "" {
|
|
t.Fatal("GetJson returned empty for a hysteria inbound without hysteriaSettings")
|
|
}
|
|
}
|
|
|
|
// A Clash subscription must carry the pinned peer certificate SHA-256 when the
|
|
// inbound configures one, matching the JSON subscription; dropping it silently
|
|
// downgrades certificate pinning for Clash subscribers.
|
|
func TestGetClashEmitsPinnedCertSha256(t *testing.T) {
|
|
seedSubDB(t)
|
|
const pin = "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"
|
|
stream := `{"network":"tcp","security":"tls","tlsSettings":{"serverName":"pin.sni","settings":{"pinnedPeerCertSha256":["` + pin + `"]}}}`
|
|
seedSubInbound(t, "pin1", "pin", 46300, 1, stream)
|
|
|
|
out, _, err := NewSubClashService(false, "", NewSubService("")).GetClash("pin1", "sub.example.com")
|
|
if err != nil {
|
|
t.Fatalf("GetClash: %v", err)
|
|
}
|
|
if !strings.Contains(out, "pin-sha256") {
|
|
t.Fatalf("Clash proxy dropped the pinned cert sha256:\n%s", out)
|
|
}
|
|
}
|