diff --git a/common/tls/common.go b/common/tls/common.go index 66cf122396..7c2216bedb 100644 --- a/common/tls/common.go +++ b/common/tls/common.go @@ -1,5 +1,23 @@ package tls +import ( + "math/rand" + "strings" + "unicode" +) + +func randomizeCase(s string) string { + var result strings.Builder + for _, c := range s { + if rand.Intn(2) == 0 { + result.WriteRune(unicode.ToUpper(c)) + } else { + result.WriteRune(unicode.ToLower(c)) + } + } + return result.String() +} + const ( VersionTLS10 = 0x0301 VersionTLS11 = 0x0302 diff --git a/common/tls/ech_client.go b/common/tls/ech_client.go index 7f72b4d8aa..0d9228273e 100644 --- a/common/tls/ech_client.go +++ b/common/tls/ech_client.go @@ -16,7 +16,7 @@ import ( cftls "github.com/sagernet/cloudflare-tls" "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-dns" + dns "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/ntp" @@ -101,7 +101,11 @@ func NewECHClient(ctx context.Context, serverAddress string, options option.Outb if options.DisableSNI { tlsConfig.ServerName = "127.0.0.1" } else { - tlsConfig.ServerName = serverName + if options.MixedCaseSNI { + tlsConfig.ServerName = randomizeCase(tlsConfig.ServerName) + } else { + tlsConfig.ServerName = serverName + } } if options.Insecure { tlsConfig.InsecureSkipVerify = options.Insecure diff --git a/common/tls/std_client.go b/common/tls/std_client.go index 90f51821e8..1a645cfc00 100644 --- a/common/tls/std_client.go +++ b/common/tls/std_client.go @@ -64,7 +64,11 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb if options.DisableSNI { tlsConfig.ServerName = "127.0.0.1" } else { - tlsConfig.ServerName = serverName + if options.MixedCaseSNI { + tlsConfig.ServerName = randomizeCase(tlsConfig.ServerName) + } else { + tlsConfig.ServerName = serverName + } } if options.Insecure { tlsConfig.InsecureSkipVerify = options.Insecure diff --git a/common/tls/utls_client.go b/common/tls/utls_client.go index be81b32c77..14dbae9409 100644 --- a/common/tls/utls_client.go +++ b/common/tls/utls_client.go @@ -127,6 +127,10 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out return nil, E.New("missing server_name or insecure=true") } + if options.MixedCaseSNI { + serverName = randomizeCase(serverName) + } + var tlsConfig utls.Config tlsConfig.Time = ntp.TimeFuncFromContext(ctx) if options.DisableSNI { diff --git a/option/tls.go b/option/tls.go index a38b4ef596..28d326ef42 100644 --- a/option/tls.go +++ b/option/tls.go @@ -20,6 +20,7 @@ type InboundTLSOptions struct { type OutboundTLSOptions struct { Enabled bool `json:"enabled,omitempty"` DisableSNI bool `json:"disable_sni,omitempty"` + MixedCaseSNI bool `json:"mixedcase_sni,omitempty"` ServerName string `json:"server_name,omitempty"` Insecure bool `json:"insecure,omitempty"` ALPN Listable[string] `json:"alpn,omitempty"` diff --git a/test/tls_test.go b/test/tls_test.go index 16a400cdab..954fd7e7c4 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -60,6 +60,7 @@ func TestUTLS(t *testing.T) { TLS: &option.OutboundTLSOptions{ Enabled: true, ServerName: "example.org", + MixedCaseSNI: true, CertificatePath: certPem, UTLS: &option.OutboundUTLSOptions{ Enabled: true,