From 0188c2d67d8aec8997755700a3586cd83f9c3c71 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= <i@sekai.icu>
Date: Tue, 21 Feb 2023 20:42:44 +0800
Subject: [PATCH] Add uTLS support for shadowtls

---
 common/singbridge/dialer_tls.go       |  6 ++--
 go.mod                                |  6 ++--
 go.sum                                | 18 ++++-------
 proxy/shadowtls/outbound.go           | 44 ++++++++++++++++++++++-----
 proxy/vless/outbound/outbound.go      |  2 +-
 transport/internet/reality/reality.go |  2 +-
 transport/internet/tcp/dialer.go      |  2 +-
 transport/internet/tls/custom.go      |  2 +-
 transport/internet/tls/grpc.go        |  2 +-
 transport/internet/tls/tls.go         |  2 +-
 10 files changed, 55 insertions(+), 31 deletions(-)

diff --git a/common/singbridge/dialer_tls.go b/common/singbridge/dialer_tls.go
index 29bd0491..90ef0c41 100644
--- a/common/singbridge/dialer_tls.go
+++ b/common/singbridge/dialer_tls.go
@@ -22,8 +22,10 @@ func NewTLSDialer(dialer internet.Dialer, clientFunc tls.CustomClientFunc) *Xray
 }
 
 func (d *XrayTLSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
+	var internetTLSConfig *tls.Config
 	var tlsConfig *gotls.Config
-	conn, err := d.dialer.Dial(tls.ContextWithCustomClient(ctx, func(conn net.Conn, config *gotls.Config) net.Conn {
+	conn, err := d.dialer.Dial(tls.ContextWithCustomClient(ctx, func(conn net.Conn, xrayConfig *tls.Config, config *gotls.Config) net.Conn {
+		internetTLSConfig = xrayConfig
 		tlsConfig = config
 		return conn
 	}), ToDestination(destination, ToNetwork(network)))
@@ -33,7 +35,7 @@ func (d *XrayTLSDialer) DialContext(ctx context.Context, network string, destina
 	if tlsConfig == nil {
 		return nil, E.New("missing TLS config")
 	}
-	return d.clientFunc(conn, tlsConfig), nil
+	return d.clientFunc(conn, internetTLSConfig, tlsConfig), nil
 }
 
 func (d *XrayTLSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
diff --git a/go.mod b/go.mod
index 89d07702..cedc65b3 100644
--- a/go.mod
+++ b/go.mod
@@ -12,10 +12,10 @@ require (
 	github.com/pelletier/go-toml v1.9.5
 	github.com/pires/go-proxyproto v0.6.2
 	github.com/quic-go/quic-go v0.32.0
-	github.com/refraction-networking/utls v1.2.2
 	github.com/sagernet/sing v0.1.6
 	github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7
-	github.com/sagernet/sing-shadowtls v0.0.0-20230221100347-75f55ea45b99
+	github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587
+	github.com/sagernet/utls v0.0.0-20230220130002-c08891932056
 	github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
 	github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb
 	github.com/stretchr/testify v1.8.1
@@ -34,7 +34,7 @@ require (
 )
 
 require (
-	github.com/andybalholm/brotli v1.0.4 // indirect
+	github.com/andybalholm/brotli v1.0.5 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 // indirect
 	github.com/francoispqt/gojay v1.2.13 // indirect
diff --git a/go.sum b/go.sum
index 88c47031..ddd36a4f 100644
--- a/go.sum
+++ b/go.sum
@@ -8,8 +8,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
-github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
+github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
@@ -138,8 +138,6 @@ github.com/quic-go/qtls-go1-20 v0.1.0 h1:d1PK3ErFy9t7zxKsG3NXBJXZjp/kMLoIb3y/kV5
 github.com/quic-go/qtls-go1-20 v0.1.0/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
 github.com/quic-go/quic-go v0.32.0 h1:lY02md31s1JgPiiyfqJijpu/UX/Iun304FI3yUqX7tA=
 github.com/quic-go/quic-go v0.32.0/go.mod h1:/fCsKANhQIeD5l76c2JFU+07gVE3KaA0FP+0zMWwfwo=
-github.com/refraction-networking/utls v1.2.2 h1:uBE6V173CwG8MQrSBpNZHAix1fxOvuLKYyjFAu3uqo0=
-github.com/refraction-networking/utls v1.2.2/go.mod h1:L1goe44KvhnTfctUffM2isnJpSjPlYShrhXDeZaoYKw=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
 github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@@ -147,14 +145,10 @@ github.com/sagernet/sing v0.1.6 h1:Qy63OUfKpcqKjfd5rPmUlj0RGjHZSK/PJn0duyCCsRg=
 github.com/sagernet/sing v0.1.6/go.mod h1:JLSXsPTGRJFo/3X7EcAOCUgJH2/gAoxSJgBsnCZRp/w=
 github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7 h1:Plup6oEiyLzY3HDqQ+QsUBzgBGdVmcsgf3t8h940z9U=
 github.com/sagernet/sing-shadowsocks v0.1.1-0.20230202035033-e3123545f2f7/go.mod h1:O5LtOs8Ivw686FqLpO0Zu+A0ROVE15VeqEK3yDRRAms=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221081357-574313aaae1d h1:yETevmRbJ6Mf9xgavSmgxo9UCdKNyplU4ubgFXqd4XU=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221081357-574313aaae1d/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221093358-af0356df4755 h1:5/GdWkRlHv00+4JrIy0ilWAw2p/EWtuH0CClC2/gko4=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221093358-af0356df4755/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221100347-75f55ea45b99 h1:LhGnlaH8bV0MCNp/LW4PDPagtkJDSAX0ftN9bJ6HMxY=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221100347-75f55ea45b99/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221110738-214729669cdc h1:T1XsW+0eNGlyy7NXY8AG2pE3d+RsdphRCH5ux0jjWf4=
-github.com/sagernet/sing-shadowtls v0.0.0-20230221110738-214729669cdc/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
+github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587 h1:OjIXlHT2bblZfp+ciupM4xY9+Ccpj9FsuHRtKRBv+Pg=
+github.com/sagernet/sing-shadowtls v0.0.0-20230221123345-78e50cd7b587/go.mod h1:Kn1VUIprdkwCgkS6SXYaLmIpKzQbqBIKJBMY+RvBhYc=
+github.com/sagernet/utls v0.0.0-20230220130002-c08891932056 h1:gDXi/0uYe8dA48UyUI1LM2la5QYN0IvsDvR2H2+kFnA=
+github.com/sagernet/utls v0.0.0-20230220130002-c08891932056/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
 github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
 github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
 github.com/seiflotfy/cuckoofilter v0.0.0-20220411075957-e3b120b3f5fb h1:XfLJSPIOUX+osiMraVgIrMR27uMXnRJWGm1+GL8/63U=
diff --git a/proxy/shadowtls/outbound.go b/proxy/shadowtls/outbound.go
index 031536f0..b96031d0 100644
--- a/proxy/shadowtls/outbound.go
+++ b/proxy/shadowtls/outbound.go
@@ -5,12 +5,15 @@ import (
 	"crypto/tls"
 
 	"github.com/sagernet/sing-shadowtls"
+	sing_common "github.com/sagernet/sing/common"
+	utls "github.com/sagernet/utls"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/session"
 	"github.com/xtls/xray-core/common/singbridge"
 	"github.com/xtls/xray-core/transport"
 	"github.com/xtls/xray-core/transport/internet"
+	internet_tls "github.com/xtls/xray-core/transport/internet/tls"
 )
 
 func init() {
@@ -60,14 +63,14 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
 
 	var client *shadowtls.Client
 	clientConfig := o.clientConfig
-	if clientConfig.Version == 3 {
-		clientConfig.Dialer = singbridge.NewTLSDialer(dialer, func(conn net.Conn, config *tls.Config) net.Conn {
-			client.SetTLSConfig(config)
-			return conn
-		})
-	} else {
-		clientConfig.Dialer = singbridge.NewDialer(dialer)
-	}
+	clientConfig.Dialer = singbridge.NewTLSDialer(dialer, func(conn net.Conn, xrayConfig *internet_tls.Config, config *tls.Config) net.Conn {
+		if fingerprint := internet_tls.GetFingerprint(xrayConfig.Fingerprint); fingerprint != nil {
+			client.SetHandshakeFunc(uTLSHandshakeFunc(config, *fingerprint))
+		} else {
+			client.SetHandshakeFunc(shadowtls.DefaultTLSHandshakeFunc(clientConfig.Password, config))
+		}
+		return conn
+	})
 	var err error
 	client, err = shadowtls.NewClient(clientConfig)
 	if err != nil {
@@ -81,3 +84,28 @@ func (o *Outbound) Process(ctx context.Context, link *transport.Link, dialer int
 
 	return singbridge.CopyConn(ctx, inboundConn, link, conn)
 }
+
+func uTLSHandshakeFunc(config *tls.Config, clientHelloID utls.ClientHelloID) shadowtls.TLSHandshakeFunc {
+	return func(ctx context.Context, conn net.Conn, sessionIDGenerator shadowtls.TLSSessionIDGeneratorFunc) error {
+		tlsConfig := &utls.Config{
+			Rand:                  config.Rand,
+			Time:                  config.Time,
+			VerifyPeerCertificate: config.VerifyPeerCertificate,
+			RootCAs:               config.RootCAs,
+			NextProtos:            config.NextProtos,
+			ServerName:            config.ServerName,
+			InsecureSkipVerify:    config.InsecureSkipVerify,
+			CipherSuites:          config.CipherSuites,
+			MinVersion:            config.MinVersion,
+			MaxVersion:            config.MaxVersion,
+			CurvePreferences: sing_common.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID {
+				return utls.CurveID(it)
+			}),
+			SessionTicketsDisabled: config.SessionTicketsDisabled,
+			Renegotiation:          utls.RenegotiationSupport(config.Renegotiation),
+			SessionIDGenerator:     sessionIDGenerator,
+		}
+		tlsConn := utls.UClient(conn, tlsConfig, clientHelloID)
+		return tlsConn.HandshakeContext(ctx)
+	}
+}
diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go
index e532dfb6..33eca284 100644
--- a/proxy/vless/outbound/outbound.go
+++ b/proxy/vless/outbound/outbound.go
@@ -11,7 +11,7 @@ import (
 	"time"
 	"unsafe"
 
-	utls "github.com/refraction-networking/utls"
+	utls "github.com/sagernet/utls"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/net"
diff --git a/transport/internet/reality/reality.go b/transport/internet/reality/reality.go
index 145f1531..04db2bd7 100644
--- a/transport/internet/reality/reality.go
+++ b/transport/internet/reality/reality.go
@@ -24,7 +24,7 @@ import (
 	"time"
 	"unsafe"
 
-	utls "github.com/refraction-networking/utls"
+	utls "github.com/sagernet/utls"
 	"github.com/xtls/reality"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/net"
diff --git a/transport/internet/tcp/dialer.go b/transport/internet/tcp/dialer.go
index 0a838506..0b6ef200 100644
--- a/transport/internet/tcp/dialer.go
+++ b/transport/internet/tcp/dialer.go
@@ -25,7 +25,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 		tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
 		customClient, loaded := tls.CustomClientFromContext(ctx)
 		if loaded {
-			conn = customClient(conn, tlsConfig)
+			conn = customClient(conn, config, tlsConfig)
 		} else {
 			if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil {
 				conn = tls.UClient(conn, tlsConfig, fingerprint)
diff --git a/transport/internet/tls/custom.go b/transport/internet/tls/custom.go
index f6468291..5969a474 100644
--- a/transport/internet/tls/custom.go
+++ b/transport/internet/tls/custom.go
@@ -9,7 +9,7 @@ import (
 
 type customClientKey struct{}
 
-type CustomClientFunc func(conn net.Conn, config *tls.Config) net.Conn
+type CustomClientFunc func(conn net.Conn, xrayConfig *Config, config *tls.Config) net.Conn
 
 func CustomClientFromContext(ctx context.Context) (CustomClientFunc, bool) {
 	client, loaded := ctx.Value(customClientKey{}).(CustomClientFunc)
diff --git a/transport/internet/tls/grpc.go b/transport/internet/tls/grpc.go
index a698196b..6368cdf0 100644
--- a/transport/internet/tls/grpc.go
+++ b/transport/internet/tls/grpc.go
@@ -7,7 +7,7 @@ import (
 	"net/url"
 	"strconv"
 
-	utls "github.com/refraction-networking/utls"
+	utls "github.com/sagernet/utls"
 	"google.golang.org/grpc/credentials"
 )
 
diff --git a/transport/internet/tls/tls.go b/transport/internet/tls/tls.go
index 2fd9a017..416df17e 100644
--- a/transport/internet/tls/tls.go
+++ b/transport/internet/tls/tls.go
@@ -5,7 +5,7 @@ import (
 	"crypto/tls"
 	"math/big"
 
-	utls "github.com/refraction-networking/utls"
+	utls "github.com/sagernet/utls"
 	"github.com/xtls/xray-core/common/buf"
 	"github.com/xtls/xray-core/common/net"
 )