diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index e7c2dc2ba..80d113c71 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -19,7 +19,6 @@ import ( shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin" - restlsC "github.com/3andne/restls-client-go" shadowsocks "github.com/metacubex/sing-shadowsocks2" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" @@ -37,7 +36,7 @@ type ShadowSocks struct { v2rayOption *v2rayObfs.Option gostOption *gost.Option shadowTLSOption *shadowtls.ShadowTLSOption - restlsConfig *restlsC.Config + restlsConfig *restls.Config } type ShadowSocksOption struct { @@ -265,7 +264,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { var gostOption *gost.Option var obfsOption *simpleObfsOption var shadowTLSOpt *shadowtls.ShadowTLSOption - var restlsConfig *restlsC.Config + var restlsConfig *restls.Config obfsMode := "" decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) @@ -350,7 +349,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) } - restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint) + restlsConfig, err = restls.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint) if err != nil { return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) } diff --git a/common/utils/slice.go b/common/utils/slice.go index 1b0fa4940..1cd033470 100644 --- a/common/utils/slice.go +++ b/common/utils/slice.go @@ -16,6 +16,17 @@ func Filter[T comparable](tSlice []T, filter func(t T) bool) []T { return result } +func Map[T any, N any](arr []T, block func(it T) N) []N { + if arr == nil { // keep nil + return nil + } + retArr := make([]N, 0, len(arr)) + for index := range arr { + retArr = append(retArr, block(arr[index])) + } + return retArr +} + func ToStringSlice(value any) ([]string, error) { strArr := make([]string, 0) switch reflect.TypeOf(value).Kind() { diff --git a/component/ca/config.go b/component/ca/config.go index 0c8391b02..7ff353343 100644 --- a/component/ca/config.go +++ b/component/ca/config.go @@ -111,11 +111,7 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) { return (*[32]byte)(fpByte), nil } -// GetTLSConfig specified fingerprint, customCA and customCAString -func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (*tls.Config, error) { - if tlsConfig == nil { - tlsConfig = &tls.Config{} - } +func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) { var certificate []byte var err error if len(customCA) > 0 { @@ -131,17 +127,35 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu if !certPool.AppendCertsFromPEM(certificate) { return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate) } - tlsConfig.RootCAs = certPool + return certPool, nil } else { - tlsConfig.RootCAs = getCertPool() + return getCertPool(), nil } +} + +func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) { + fingerprintBytes, err := convertFingerprint(fingerprint) + if err != nil { + return nil, err + } + return verifyFingerprint(fingerprintBytes), nil +} + +// GetTLSConfig specified fingerprint, customCA and customCAString +func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (_ *tls.Config, err error) { + if tlsConfig == nil { + tlsConfig = &tls.Config{} + } + tlsConfig.RootCAs, err = GetCertPool(customCA, customCAString) + if err != nil { + return nil, err + } + if len(fingerprint) > 0 { - var fingerprintBytes *[32]byte - fingerprintBytes, err = convertFingerprint(fingerprint) + tlsConfig.VerifyPeerCertificate, err = NewFingerprintVerifier(fingerprint) if err != nil { return nil, err } - tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes) tlsConfig.InsecureSkipVerify = true } return tlsConfig, nil diff --git a/component/tls/utls.go b/component/tls/utls.go index 31733e50a..b38849e51 100644 --- a/component/tls/utls.go +++ b/component/tls/utls.go @@ -4,15 +4,16 @@ import ( "crypto/tls" "net" + "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/log" utls "github.com/metacubex/utls" "github.com/mroth/weightedrand/v2" ) -type UConn struct { - *utls.UConn -} +type UConn = utls.UConn + +const VersionTLS13 = utls.VersionTLS13 type UClientHelloID struct { *utls.ClientHelloID @@ -21,13 +22,8 @@ type UClientHelloID struct { var initRandomFingerprint UClientHelloID var initUtlsClient string -func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn { - utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{ - Client: fingerprint.Client, - Version: fingerprint.Version, - Seed: fingerprint.Seed, - }) - return &UConn{UConn: utlsConn} +func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn { + return utls.UClient(c, config, *fingerprint.ClientHelloID) } func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) { @@ -95,18 +91,43 @@ func init() { Fingerprints["randomized"] = UClientHelloID{&randomized} } -func copyConfig(c *tls.Config) *utls.Config { +func UCertificates(it tls.Certificate) utls.Certificate { + return utls.Certificate{ + Certificate: it.Certificate, + PrivateKey: it.PrivateKey, + SupportedSignatureAlgorithms: utils.Map(it.SupportedSignatureAlgorithms, func(it tls.SignatureScheme) utls.SignatureScheme { + return utls.SignatureScheme(it) + }), + OCSPStaple: it.OCSPStaple, + SignedCertificateTimestamps: it.SignedCertificateTimestamps, + Leaf: it.Leaf, + } +} + +func UConfig(config *tls.Config) *utls.Config { return &utls.Config{ - RootCAs: c.RootCAs, - ServerName: c.ServerName, - InsecureSkipVerify: c.InsecureSkipVerify, - VerifyPeerCertificate: c.VerifyPeerCertificate, + Rand: config.Rand, + Time: config.Time, + Certificates: utils.Map(config.Certificates, UCertificates), + 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: utils.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID { + return utls.CurveID(it) + }), + SessionTicketsDisabled: config.SessionTicketsDisabled, + Renegotiation: utls.RenegotiationSupport(config.Renegotiation), } } // BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN. // Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go -func (c *UConn) BuildWebsocketHandshakeState() error { +func BuildWebsocketHandshakeState(c *UConn) error { // Build the handshake state. This will apply every variable of the TLS of the // fingerprint in the UConn if err := c.BuildHandshakeState(); err != nil { diff --git a/go.mod b/go.mod index f40e0b89d..1af1accd9 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 - github.com/metacubex/utls v1.6.8-alpha.6 + github.com/metacubex/utls v1.7.0-alpha.1 github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 github.com/miekg/dns v1.1.63 github.com/mroth/weightedrand/v2 v2.1.0 diff --git a/go.sum b/go.sum index c7e852b00..4b7965733 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0 github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= -github.com/metacubex/utls v1.6.8-alpha.6 h1:5ZdZNiZFkKKgEcuPOOROIc8bA4dX2VJHoY3gajSnSaU= -github.com/metacubex/utls v1.6.8-alpha.6/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk= +github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4= +github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= diff --git a/listener/sing_vless/server.go b/listener/sing_vless/server.go index 137c8beb9..c16bc97bf 100644 --- a/listener/sing_vless/server.go +++ b/listener/sing_vless/server.go @@ -22,14 +22,13 @@ import ( mihomoVMess "github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/sing-vmess/vless" - utls "github.com/metacubex/utls" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/metadata" ) func init() { vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { - tlsConn, loaded := common.Cast[*reality.Conn](conn) + tlsConn, loaded := common.Cast[*reality.Conn](conn) // *utls.Conn if !loaded { return } @@ -37,15 +36,7 @@ func init() { }) vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { - tlsConn, loaded := common.Cast[*utls.UConn](conn) - if !loaded { - return - } - return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn) - }) - - vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { - tlsConn, loaded := common.Cast[*tlsC.UConn](conn) + tlsConn, loaded := common.Cast[*tlsC.UConn](conn) // *utls.UConn if !loaded { return } diff --git a/transport/gun/gun.go b/transport/gun/gun.go index 8889baa75..68f4b2d9a 100644 --- a/transport/gun/gun.go +++ b/transport/gun/gun.go @@ -244,7 +244,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint stri if len(clientFingerprint) != 0 { if realityConfig == nil { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - utlsConn := tlsC.UClient(pconn, cfg, fingerprint) + utlsConn := tlsC.UClient(pconn, tlsC.UConfig(cfg), fingerprint) if err := utlsConn.HandshakeContext(ctx); err != nil { pconn.Close() return nil, err diff --git a/transport/restls/restls.go b/transport/restls/restls.go index 0f3ba8ac7..95ac69890 100644 --- a/transport/restls/restls.go +++ b/transport/restls/restls.go @@ -19,8 +19,12 @@ func (r *Restls) Upstream() any { return r.UConn.NetConn() } +type Config = tls.Config + +var NewRestlsConfig = tls.NewRestlsConfig + // NewRestls return a Restls Connection -func NewRestls(ctx context.Context, conn net.Conn, config *tls.Config) (net.Conn, error) { +func NewRestls(ctx context.Context, conn net.Conn, config *Config) (net.Conn, error) { clientHellowID := tls.HelloChrome_Auto if config != nil { clientIDPtr := config.ClientID.Load() diff --git a/transport/sing-shadowtls/shadowtls.go b/transport/sing-shadowtls/shadowtls.go index a628e7b16..2f634bcbb 100644 --- a/transport/sing-shadowtls/shadowtls.go +++ b/transport/sing-shadowtls/shadowtls.go @@ -11,7 +11,6 @@ import ( "github.com/metacubex/sing-shadowtls" utls "github.com/metacubex/utls" - sing_common "github.com/sagernet/sing/common" ) const ( @@ -60,32 +59,15 @@ func NewShadowTLS(ctx context.Context, conn net.Conn, option *ShadowTLSOption) ( func uTLSHandshakeFunc(config *tls.Config, clientFingerprint string) 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, - } + tlsConfig := tlsC.UConfig(config) + tlsConfig.SessionIDGenerator = sessionIDGenerator clientFingerprint := clientFingerprint if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 { clientFingerprint = tlsC.GetGlobalFingerprint() } if len(clientFingerprint) != 0 { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - clientHelloID := *fingerprint.ClientHelloID - tlsConn := utls.UClient(conn, tlsConfig, clientHelloID) + tlsConn := tlsC.UClient(conn, tlsConfig, fingerprint) return tlsConn.HandshakeContext(ctx) } } diff --git a/transport/vless/vision/conn.go b/transport/vless/vision/conn.go index 556626594..56684808c 100644 --- a/transport/vless/vision/conn.go +++ b/transport/vless/vision/conn.go @@ -12,10 +12,10 @@ import ( "github.com/metacubex/mihomo/common/buf" N "github.com/metacubex/mihomo/common/net" + tlsC "github.com/metacubex/mihomo/component/tls" "github.com/metacubex/mihomo/log" "github.com/gofrs/uuid/v5" - utls "github.com/metacubex/utls" ) var ( @@ -187,8 +187,8 @@ func (vc *Conn) WriteBuffer(buffer *buf.Buffer) (err error) { buffer.Release() return ErrNotTLS13 } - case *utls.UConn: - if underlying.ConnectionState().Version != utls.VersionTLS13 { + case *tlsC.UConn: + if underlying.ConnectionState().Version != tlsC.VersionTLS13 { buffer.Release() return ErrNotTLS13 } diff --git a/transport/vless/vision/vision.go b/transport/vless/vision/vision.go index 3b9e9379e..ac0b05345 100644 --- a/transport/vless/vision/vision.go +++ b/transport/vless/vision/vision.go @@ -14,7 +14,6 @@ import ( tlsC "github.com/metacubex/mihomo/component/tls" "github.com/gofrs/uuid/v5" - utls "github.com/metacubex/utls" "github.com/sagernet/sing/common" ) @@ -46,16 +45,10 @@ func NewConn(conn connWithUpstream, userUUID *uuid.UUID) (*Conn, error) { c.tlsConn = underlying t = reflect.TypeOf(underlying).Elem() p = unsafe.Pointer(underlying) - case *utls.UConn: - //log.Debugln("type *utls.UConn") - c.Conn = underlying.NetConn() - c.tlsConn = underlying - t = reflect.TypeOf(underlying.Conn).Elem() - p = unsafe.Pointer(underlying.Conn) case *tlsC.UConn: //log.Debugln("type *tlsC.UConn") c.Conn = underlying.NetConn() - c.tlsConn = underlying.UConn + c.tlsConn = underlying t = reflect.TypeOf(underlying.Conn).Elem() //log.Debugln("t:%v", t) p = unsafe.Pointer(underlying.Conn) diff --git a/transport/vmess/tls.go b/transport/vmess/tls.go index ff622995a..69871bb8e 100644 --- a/transport/vmess/tls.go +++ b/transport/vmess/tls.go @@ -39,7 +39,7 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn if len(clientFingerprint) != 0 { if cfg.Reality == nil { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint) + utlsConn := tlsC.UClient(conn, tlsC.UConfig(tlsConfig), fingerprint) err = utlsConn.HandshakeContext(ctx) if err != nil { return nil, err diff --git a/transport/vmess/websocket.go b/transport/vmess/websocket.go index 43b695ee9..6a8963fd4 100644 --- a/transport/vmess/websocket.go +++ b/transport/vmess/websocket.go @@ -360,8 +360,8 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, } if len(clientFingerprint) != 0 { if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists { - utlsConn := tlsC.UClient(conn, config, fingerprint) - if err = utlsConn.BuildWebsocketHandshakeState(); err != nil { + utlsConn := tlsC.UClient(conn, tlsC.UConfig(config), fingerprint) + if err = tlsC.BuildWebsocketHandshakeState(utlsConn); err != nil { return nil, fmt.Errorf("parse url %s error: %w", c.Path, err) } conn = utlsConn