Browse Source

MITM: Allow using local received SNI in the outgoing `serverName` & `verifyPeerCertInNames`

https://github.com/XTLS/Xray-core/issues/4348#issuecomment-2637370175

Local received SNI was sent by browser/app.

In freedom RAW's `tlsSettings`, set `"serverName": "fromMitm"` to forward it to the real website.

In freedom RAW's `tlsSettings`, set `"verifyPeerCertInNames": ["fromMitm"]` to use all possible names to verify the certificate.
RPRX 8 months ago
parent
commit
c6a31f457c

+ 12 - 0
common/session/context.go

@@ -24,6 +24,7 @@ const (
 	allowedNetworkKey         ctx.SessionKey = 9
 	handlerSessionKey         ctx.SessionKey = 10
 	mitmAlpn11Key             ctx.SessionKey = 11
+	mitmServerNameKey         ctx.SessionKey = 12
 )
 
 func ContextWithInbound(ctx context.Context, inbound *Inbound) context.Context {
@@ -174,3 +175,14 @@ func MitmAlpn11FromContext(ctx context.Context) bool {
 	}
 	return false
 }
+
+func ContextWithMitmServerName(ctx context.Context, serverName string) context.Context {
+	return context.WithValue(ctx, mitmServerNameKey, serverName)
+}
+
+func MitmServerNameFromContext(ctx context.Context) string {
+	if val, ok := ctx.Value(mitmServerNameKey).(string); ok {
+		return val
+	}
+	return ""
+}

+ 5 - 3
infra/conf/transport_internet.go

@@ -411,6 +411,7 @@ type TLSConfig struct {
 	CurvePreferences                     *StringList      `json:"curvePreferences"`
 	MasterKeyLog                         string           `json:"masterKeyLog"`
 	ServerNameToVerify                   string           `json:"serverNameToVerify"`
+	VerifyPeerCertInNames                []string         `json:"verifyPeerCertInNames"`
 }
 
 // Build implements Buildable.
@@ -469,10 +470,11 @@ func (c *TLSConfig) Build() (proto.Message, error) {
 	}
 
 	config.MasterKeyLog = c.MasterKeyLog
-	config.ServerNameToVerify = c.ServerNameToVerify
-	if config.ServerNameToVerify != "" && config.Fingerprint == "unsafe" {
-		return nil, errors.New(`serverNameToVerify only works with uTLS for now`)
+
+	if c.ServerNameToVerify != "" {
+		return nil, errors.PrintRemovedFeatureError("serverNameToVerify", "verifyPeerCertInNames")
 	}
+	config.VerifyPeerCertInNames = c.VerifyPeerCertInNames
 
 	return config, nil
 }

+ 7 - 11
proxy/dokodemo/dokodemo.go

@@ -64,10 +64,6 @@ func (d *DokodemoDoor) policy() policy.Session {
 	return p
 }
 
-type hasHandshakeAddressContext interface {
-	HandshakeAddressContext(ctx context.Context) net.Address
-}
-
 // Process implements proxy.Inbound.
 func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
 	errors.LogDebug(ctx, "processing connection from: ", conn.RemoteAddr())
@@ -87,14 +83,14 @@ func (d *DokodemoDoor) Process(ctx context.Context, network net.Network, conn st
 				destinationOverridden = true
 			}
 		}
-		if handshake, ok := conn.(hasHandshakeAddressContext); ok && !destinationOverridden {
-			addr := handshake.HandshakeAddressContext(ctx)
-			if addr != nil {
-				dest.Address = addr
-				if conn.(*tls.Conn).ConnectionState().NegotiatedProtocol == "http/1.1" {
-					ctx = session.ContextWithMitmAlpn11(ctx, true)
-				}
+		if tlsConn, ok := conn.(tls.Interface); ok && !destinationOverridden {
+			if serverName := tlsConn.HandshakeContextServerName(ctx); serverName != "" {
+				dest.Address = net.DomainAddress(serverName)
 				destinationOverridden = true
+				ctx = session.ContextWithMitmServerName(ctx, serverName)
+			}
+			if tlsConn.NegotiatedProtocol() == "http/1.1" {
+				ctx = session.ContextWithMitmAlpn11(ctx, true)
 			}
 		}
 	}

+ 29 - 5
transport/internet/tcp/dialer.go

@@ -14,6 +14,10 @@ import (
 	"github.com/xtls/xray-core/transport/internet/tls"
 )
 
+func IsFromMitm(str string) bool {
+	return strings.ToLower(str) == "frommitm"
+}
+
 // Dial dials a new TCP connection to the given destination.
 func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) {
 	errors.LogInfo(ctx, "dialing TCP to ", dest)
@@ -23,11 +27,28 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 	}
 
 	if config := tls.ConfigFromStreamSettings(streamSettings); config != nil {
+		mitmServerName := session.MitmServerNameFromContext(ctx)
+		mitmAlpn11 := session.MitmAlpn11FromContext(ctx)
 		tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
+		if IsFromMitm(tlsConfig.ServerName) {
+			tlsConfig.ServerName = mitmServerName
+		}
+		if r, ok := tlsConfig.Rand.(*tls.RandCarrier); ok && len(r.VerifyPeerCertInNames) > 0 && IsFromMitm(r.VerifyPeerCertInNames[0]) {
+			r.VerifyPeerCertInNames = r.VerifyPeerCertInNames[1:]
+			after := mitmServerName
+			for {
+				if len(after) > 0 {
+					r.VerifyPeerCertInNames = append(r.VerifyPeerCertInNames, after)
+				}
+				_, after, _ = strings.Cut(after, ".")
+				if !strings.Contains(after, ".") {
+					break
+				}
+			}
+		}
 		if fingerprint := tls.GetFingerprint(config.Fingerprint); fingerprint != nil {
 			conn = tls.UClient(conn, tlsConfig, fingerprint)
-			if len(tlsConfig.NextProtos) == 1 && (tlsConfig.NextProtos[0] == "http/1.1" ||
-				(strings.ToLower(tlsConfig.NextProtos[0]) == "frommitm" && session.MitmAlpn11FromContext(ctx))) {
+			if len(tlsConfig.NextProtos) == 1 && (tlsConfig.NextProtos[0] == "http/1.1" || (IsFromMitm(tlsConfig.NextProtos[0]) && mitmAlpn11)) {
 				if err := conn.(*tls.UConn).WebsocketHandshakeContext(ctx); err != nil {
 					return nil, err
 				}
@@ -37,14 +58,17 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 				}
 			}
 		} else {
-			if len(tlsConfig.NextProtos) == 1 && strings.ToLower(tlsConfig.NextProtos[0]) == "frommitm" {
-				if session.MitmAlpn11FromContext(ctx) {
-					tlsConfig.NextProtos = []string{"http/1.1"} // new slice
+			if len(tlsConfig.NextProtos) == 1 && IsFromMitm(tlsConfig.NextProtos[0]) {
+				if mitmAlpn11 {
+					tlsConfig.NextProtos[0] = "http/1.1"
 				} else {
 					tlsConfig.NextProtos = nil
 				}
 			}
 			conn = tls.Client(conn, tlsConfig)
+			if err := conn.(*tls.Conn).HandshakeContext(ctx); err != nil {
+				return nil, err
+			}
 		}
 	} else if config := reality.ConfigFromStreamSettings(streamSettings); config != nil {
 		if conn, err = reality.UClient(conn, config, ctx, dest); err != nil {

+ 49 - 11
transport/internet/tls/config.go

@@ -9,6 +9,7 @@ import (
 	"crypto/x509"
 	"encoding/base64"
 	"os"
+	"slices"
 	"strings"
 	"sync"
 	"time"
@@ -277,10 +278,35 @@ func (c *Config) parseServerName() string {
 	return c.ServerName
 }
 
-func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
-	if c.PinnedPeerCertificateChainSha256 != nil {
+func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
+	if r.VerifyPeerCertInNames != nil {
+		if len(r.VerifyPeerCertInNames) > 0 {
+			certs := make([]*x509.Certificate, len(rawCerts))
+			for i, asn1Data := range rawCerts {
+				certs[i], _ = x509.ParseCertificate(asn1Data)
+			}
+			opts := x509.VerifyOptions{
+				Roots:         r.RootCAs,
+				CurrentTime:   time.Now(),
+				Intermediates: x509.NewCertPool(),
+			}
+			for _, cert := range certs[1:] {
+				opts.Intermediates.AddCert(cert)
+			}
+			for _, opts.DNSName = range r.VerifyPeerCertInNames {
+				if _, err := certs[0].Verify(opts); err == nil {
+					return nil
+				}
+			}
+		}
+		if r.PinnedPeerCertificateChainSha256 == nil {
+			errors.New("peer cert is invalid.")
+		}
+	}
+
+	if r.PinnedPeerCertificateChainSha256 != nil {
 		hashValue := GenerateCertChainHash(rawCerts)
-		for _, v := range c.PinnedPeerCertificateChainSha256 {
+		for _, v := range r.PinnedPeerCertificateChainSha256 {
 			if hmac.Equal(hashValue, v) {
 				return nil
 			}
@@ -288,11 +314,11 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert
 		return errors.New("peer cert is unrecognized: ", base64.StdEncoding.EncodeToString(hashValue))
 	}
 
-	if c.PinnedPeerCertificatePublicKeySha256 != nil {
+	if r.PinnedPeerCertificatePublicKeySha256 != nil {
 		for _, v := range verifiedChains {
 			for _, cert := range v {
 				publicHash := GenerateCertPublicKeyHash(cert)
-				for _, c := range c.PinnedPeerCertificatePublicKeySha256 {
+				for _, c := range r.PinnedPeerCertificatePublicKeySha256 {
 					if hmac.Equal(publicHash, c) {
 						return nil
 					}
@@ -305,7 +331,10 @@ func (c *Config) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Cert
 }
 
 type RandCarrier struct {
-	ServerNameToVerify string
+	RootCAs                              *x509.CertPool
+	VerifyPeerCertInNames                []string
+	PinnedPeerCertificateChainSha256     [][]byte
+	PinnedPeerCertificatePublicKeySha256 [][]byte
 }
 
 func (r *RandCarrier) Read(p []byte) (n int, err error) {
@@ -329,16 +358,25 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
 		}
 	}
 
+	randCarrier := &RandCarrier{
+		RootCAs:                              root,
+		VerifyPeerCertInNames:                slices.Clone(c.VerifyPeerCertInNames),
+		PinnedPeerCertificateChainSha256:     c.PinnedPeerCertificateChainSha256,
+		PinnedPeerCertificatePublicKeySha256: c.PinnedPeerCertificatePublicKeySha256,
+	}
 	config := &tls.Config{
-		Rand: &RandCarrier{
-			ServerNameToVerify: c.ServerNameToVerify,
-		},
+		Rand:                   randCarrier,
 		ClientSessionCache:     globalSessionCache,
 		RootCAs:                root,
 		InsecureSkipVerify:     c.AllowInsecure,
-		NextProtos:             c.NextProtocol,
+		NextProtos:             slices.Clone(c.NextProtocol),
 		SessionTicketsDisabled: !c.EnableSessionResumption,
-		VerifyPeerCertificate:  c.verifyPeerCert,
+		VerifyPeerCertificate:  randCarrier.verifyPeerCert,
+	}
+	if len(c.VerifyPeerCertInNames) > 0 {
+		config.InsecureSkipVerify = true
+	} else {
+		randCarrier.VerifyPeerCertInNames = nil
 	}
 
 	for _, opt := range opts {

+ 26 - 24
transport/internet/tls/config.pb.go

@@ -202,20 +202,21 @@ type Config struct {
 	// TLS Client Hello fingerprint (uTLS).
 	Fingerprint      string `protobuf:"bytes,11,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"`
 	RejectUnknownSni bool   `protobuf:"varint,12,opt,name=reject_unknown_sni,json=rejectUnknownSni,proto3" json:"reject_unknown_sni,omitempty"`
-	// @Document A pinned certificate chain sha256 hash.
-	// @Document If the server's hash does not match this value, the connection will be aborted.
-	// @Document This value replace allow_insecure.
+	// @Document Some certificate chain sha256 hashes.
+	// @Document After normal validation or allow_insecure, if the server's cert chain hash does not match any of these values, the connection will be aborted.
 	// @Critical
 	PinnedPeerCertificateChainSha256 [][]byte `protobuf:"bytes,13,rep,name=pinned_peer_certificate_chain_sha256,json=pinnedPeerCertificateChainSha256,proto3" json:"pinned_peer_certificate_chain_sha256,omitempty"`
-	// @Document A pinned certificate public key sha256 hash.
-	// @Document If the server's public key hash does not match this value, the connection will be aborted.
-	// @Document This value replace allow_insecure.
+	// @Document Some certificate public key sha256 hashes.
+	// @Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted.
 	// @Critical
 	PinnedPeerCertificatePublicKeySha256 [][]byte `protobuf:"bytes,14,rep,name=pinned_peer_certificate_public_key_sha256,json=pinnedPeerCertificatePublicKeySha256,proto3" json:"pinned_peer_certificate_public_key_sha256,omitempty"`
 	MasterKeyLog                         string   `protobuf:"bytes,15,opt,name=master_key_log,json=masterKeyLog,proto3" json:"master_key_log,omitempty"`
 	// Lists of string as CurvePreferences values.
-	CurvePreferences   []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"`
-	ServerNameToVerify string   `protobuf:"bytes,17,opt,name=server_name_to_verify,json=serverNameToVerify,proto3" json:"server_name_to_verify,omitempty"`
+	CurvePreferences []string `protobuf:"bytes,16,rep,name=curve_preferences,json=curvePreferences,proto3" json:"curve_preferences,omitempty"`
+	// @Document Replaces server_name to verify the peer cert.
+	// @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
+	// @Critical
+	VerifyPeerCertInNames []string `protobuf:"bytes,17,rep,name=verify_peer_cert_in_names,json=verifyPeerCertInNames,proto3" json:"verify_peer_cert_in_names,omitempty"`
 }
 
 func (x *Config) Reset() {
@@ -353,11 +354,11 @@ func (x *Config) GetCurvePreferences() []string {
 	return nil
 }
 
-func (x *Config) GetServerNameToVerify() string {
+func (x *Config) GetVerifyPeerCertInNames() []string {
 	if x != nil {
-		return x.ServerNameToVerify
+		return x.VerifyPeerCertInNames
 	}
-	return ""
+	return nil
 }
 
 var File_transport_internet_tls_config_proto protoreflect.FileDescriptor
@@ -391,7 +392,7 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
 	0x4e, 0x43, 0x49, 0x50, 0x48, 0x45, 0x52, 0x4d, 0x45, 0x4e, 0x54, 0x10, 0x00, 0x12, 0x14, 0x0a,
 	0x10, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x49, 0x46,
 	0x59, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x55, 0x54, 0x48, 0x4f, 0x52, 0x49, 0x54, 0x59,
-	0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x93, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
+	0x5f, 0x49, 0x53, 0x53, 0x55, 0x45, 0x10, 0x02, 0x22, 0x9a, 0x06, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
 	0x66, 0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x6e, 0x73,
 	0x65, 0x63, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x6c,
 	0x6f, 0x77, 0x49, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, 0x63, 0x65,
@@ -437,18 +438,19 @@ var file_transport_internet_tls_config_proto_rawDesc = []byte{
 	0x52, 0x0c, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x4c, 0x6f, 0x67, 0x12, 0x2b,
 	0x0a, 0x11, 0x63, 0x75, 0x72, 0x76, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,
 	0x63, 0x65, 0x73, 0x18, 0x10, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x75, 0x72, 0x76, 0x65,
-	0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x31, 0x0a, 0x15, 0x73,
-	0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x76, 0x65,
-	0x72, 0x69, 0x66, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x65, 0x72, 0x76,
-	0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x54, 0x6f, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x42, 0x73,
-	0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73,
-	0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c,
-	0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
-	0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74,
-	0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65,
-	0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61,
-	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
-	0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x19, 0x76,
+	0x65, 0x72, 0x69, 0x66, 0x79, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x5f,
+	0x69, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x11, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15,
+	0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x65, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x49, 0x6e,
+	0x4e, 0x61, 0x6d, 0x65, 0x73, 0x42, 0x73, 0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65,
+	0x72, 0x6e, 0x65, 0x74, 0x2e, 0x74, 0x6c, 0x73, 0x50, 0x01, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68,
+	0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79,
+	0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f,
+	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x74, 0x6c, 0x73, 0xaa, 0x02, 0x1b, 0x58,
+	0x72, 0x61, 0x79, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x49, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x54, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
 }
 
 var (

+ 9 - 7
transport/internet/tls/config.proto

@@ -69,16 +69,14 @@ message Config {
 
   bool reject_unknown_sni = 12;
   
-  /* @Document A pinned certificate chain sha256 hash.
-     @Document If the server's hash does not match this value, the connection will be aborted.
-     @Document This value replace allow_insecure.
+  /* @Document Some certificate chain sha256 hashes.
+     @Document After normal validation or allow_insecure, if the server's cert chain hash does not match any of these values, the connection will be aborted.
      @Critical
   */
   repeated bytes pinned_peer_certificate_chain_sha256 = 13;
 
-  /* @Document A pinned certificate public key sha256 hash.
-     @Document If the server's public key hash does not match this value, the connection will be aborted.
-     @Document This value replace allow_insecure.
+  /* @Document Some certificate public key sha256 hashes.
+     @Document After normal validation (required), if the verified cert's public key hash does not match any of these values, the connection will be aborted.
      @Critical
   */
   repeated bytes pinned_peer_certificate_public_key_sha256 = 14;
@@ -88,5 +86,9 @@ message Config {
   // Lists of string as CurvePreferences values.
   repeated string curve_preferences = 16;
 
-  string server_name_to_verify = 17;
+  /* @Document Replaces server_name to verify the peer cert.
+     @Document After allow_insecure (automatically), if the server's cert can't be verified by any of these names, pinned_peer_certificate_chain_sha256 will be tried.
+     @Critical
+  */
+  repeated string verify_peer_cert_in_names = 17;
 }

+ 13 - 24
transport/internet/tls/tls.go

@@ -16,6 +16,7 @@ type Interface interface {
 	net.Conn
 	HandshakeContext(ctx context.Context) error
 	VerifyHostname(host string) error
+	HandshakeContextServerName(ctx context.Context) string
 	NegotiatedProtocol() string
 }
 
@@ -43,15 +44,11 @@ func (c *Conn) WriteMultiBuffer(mb buf.MultiBuffer) error {
 	return err
 }
 
-func (c *Conn) HandshakeAddressContext(ctx context.Context) net.Address {
+func (c *Conn) HandshakeContextServerName(ctx context.Context) string {
 	if err := c.HandshakeContext(ctx); err != nil {
-		return nil
+		return ""
 	}
-	state := c.ConnectionState()
-	if state.ServerName == "" {
-		return nil
-	}
-	return net.ParseAddress(state.ServerName)
+	return c.ConnectionState().ServerName
 }
 
 func (c *Conn) NegotiatedProtocol() string {
@@ -85,15 +82,11 @@ func (c *UConn) Close() error {
 	return c.Conn.Close()
 }
 
-func (c *UConn) HandshakeAddressContext(ctx context.Context) net.Address {
+func (c *UConn) HandshakeContextServerName(ctx context.Context) string {
 	if err := c.HandshakeContext(ctx); err != nil {
-		return nil
+		return ""
 	}
-	state := c.ConnectionState()
-	if state.ServerName == "" {
-		return nil
-	}
-	return net.ParseAddress(state.ServerName)
+	return c.ConnectionState().ServerName
 }
 
 // WebsocketHandshake basically calls UConn.Handshake inside it but it will only send
@@ -134,17 +127,13 @@ func UClient(c net.Conn, config *tls.Config, fingerprint *utls.ClientHelloID) ne
 }
 
 func copyConfig(c *tls.Config) *utls.Config {
-	serverNameToVerify := ""
-	if r, ok := c.Rand.(*RandCarrier); ok {
-		serverNameToVerify = r.ServerNameToVerify
-	}
 	return &utls.Config{
-		RootCAs:                    c.RootCAs,
-		ServerName:                 c.ServerName,
-		InsecureSkipVerify:         c.InsecureSkipVerify,
-		VerifyPeerCertificate:      c.VerifyPeerCertificate,
-		KeyLogWriter:               c.KeyLogWriter,
-		InsecureServerNameToVerify: serverNameToVerify,
+		Rand:                  c.Rand,
+		RootCAs:               c.RootCAs,
+		ServerName:            c.ServerName,
+		InsecureSkipVerify:    c.InsecureSkipVerify,
+		VerifyPeerCertificate: c.VerifyPeerCertificate,
+		KeyLogWriter:          c.KeyLogWriter,
 	}
 }