From 6108000cce1a4482d7eec6a39ab211c2045e7c16 Mon Sep 17 00:00:00 2001 From: wenyifan Date: Tue, 14 Nov 2023 00:32:42 +0800 Subject: [PATCH] utls --- dialer/tls/dialer.go | 18 ++++++-- dialer/ws/dialer.go | 107 +++++++++++++++++++++++++++++++++++++++++-- go.mod | 1 + go.sum | 1 + 4 files changed, 120 insertions(+), 7 deletions(-) diff --git a/dialer/tls/dialer.go b/dialer/tls/dialer.go index fd9967c..ebf990e 100644 --- a/dialer/tls/dialer.go +++ b/dialer/tls/dialer.go @@ -2,7 +2,7 @@ package tls import ( "context" - "crypto/tls" + tls "github.com/refraction-networking/utls" "net" "time" @@ -57,8 +57,20 @@ func (d *tlsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dia conn.SetDeadline(time.Now().Add(d.md.handshakeTimeout)) defer conn.SetDeadline(time.Time{}) } - - tlsConn := tls.Client(conn, d.options.TLSConfig) + tlsConfig := d.options.TLSConfig + utlsConf := &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs} + if len(tlsConfig.Certificates) > 0 { + for _, certificate := range tlsConfig.Certificates { + utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{ + Certificate: certificate.Certificate, + PrivateKey: certificate.PrivateKey, + OCSPStaple: certificate.OCSPStaple, + SignedCertificateTimestamps: certificate.SignedCertificateTimestamps, + Leaf: certificate.Leaf, + }) + } + } + tlsConn := tls.UClient(conn, utlsConf, tls.HelloChrome_102) if err := tlsConn.HandshakeContext(ctx); err != nil { conn.Close() return nil, err diff --git a/dialer/ws/dialer.go b/dialer/ws/dialer.go index ccc5bc5..cac0dbc 100644 --- a/dialer/ws/dialer.go +++ b/dialer/ws/dialer.go @@ -2,6 +2,7 @@ package ws import ( "context" + tls "github.com/refraction-networking/utls" "net" "net/url" "time" @@ -91,13 +92,38 @@ func (d *wsDialer) Handshake(ctx context.Context, conn net.Conn, options ...dial }, } - url := url.URL{Scheme: "ws", Host: host, Path: d.md.path} + urlObj := url.URL{Scheme: "ws", Host: host, Path: d.md.path} if d.tlsEnabled { - url.Scheme = "wss" + urlObj.Scheme = "wss" dialer.TLSClientConfig = d.options.TLSConfig + tlsConfig := d.options.TLSConfig + dialer.NetDialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + utlsConf := &tls.Config{InsecureSkipVerify: tlsConfig.InsecureSkipVerify, ServerName: tlsConfig.ServerName, ClientAuth: tls.ClientAuthType(tlsConfig.ClientAuth), ClientCAs: tlsConfig.ClientCAs, RootCAs: tlsConfig.RootCAs} + if len(tlsConfig.Certificates) > 0 { + for _, certificate := range tlsConfig.Certificates { + utlsConf.Certificates = append(utlsConf.Certificates, tls.Certificate{ + Certificate: certificate.Certificate, + PrivateKey: certificate.PrivateKey, + OCSPStaple: certificate.OCSPStaple, + SignedCertificateTimestamps: certificate.SignedCertificateTimestamps, + Leaf: certificate.Leaf, + }) + } + } + client := tls.UClient(conn, utlsConf, tls.HelloCustom) + client.ApplyPreset(newWsSpec()) + err := client.Handshake() + if err != nil { + return nil, err + } + return client, nil + } } - - c, resp, err := dialer.DialContext(ctx, url.String(), d.md.header) + urlStr, errUnescape := url.QueryUnescape(urlObj.String()) + if errUnescape != nil { + d.options.Logger.Debugf("[ws] URL QueryUnescape Error URL.String() -> %s", urlObj.String()) + } + c, resp, err := dialer.DialContext(ctx, urlStr, d.md.header) if err != nil { return nil, err } @@ -133,3 +159,76 @@ func (d *wsDialer) keepalive(conn ws_util.WebsocketConn) { conn.SetWriteDeadline(time.Time{}) } } + +func newWsSpec() *tls.ClientHelloSpec { + return &tls.ClientHelloSpec{ + CipherSuites: []uint16{ + tls.GREASE_PLACEHOLDER, + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + }, + CompressionMethods: []byte{ + 0x00, // compressionNone + }, + Extensions: []tls.TLSExtension{ + &tls.UtlsGREASEExtension{}, + &tls.SNIExtension{}, + &tls.ExtendedMasterSecretExtension{}, + &tls.RenegotiationInfoExtension{Renegotiation: tls.RenegotiateOnceAsClient}, + &tls.SupportedCurvesExtension{[]tls.CurveID{ + tls.GREASE_PLACEHOLDER, + tls.X25519, + tls.CurveP256, + tls.CurveP384, + }}, + &tls.SupportedPointsExtension{SupportedPoints: []byte{ + 0x00, // pointFormatUncompressed + }}, + &tls.SessionTicketExtension{}, + &tls.ALPNExtension{AlpnProtocols: []string{"http/1.1"}}, + &tls.StatusRequestExtension{}, + &tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{ + tls.ECDSAWithP256AndSHA256, + tls.PSSWithSHA256, + tls.PKCS1WithSHA256, + tls.ECDSAWithP384AndSHA384, + tls.PSSWithSHA384, + tls.PKCS1WithSHA384, + tls.PSSWithSHA512, + tls.PKCS1WithSHA512, + }}, + &tls.SCTExtension{}, + &tls.KeyShareExtension{[]tls.KeyShare{ + {Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}}, + {Group: tls.X25519}, + }}, + &tls.PSKKeyExchangeModesExtension{[]uint8{ + tls.PskModeDHE, + }}, + &tls.SupportedVersionsExtension{[]uint16{ + tls.GREASE_PLACEHOLDER, + tls.VersionTLS13, + tls.VersionTLS12, + }}, + &tls.UtlsCompressCertExtension{[]tls.CertCompressionAlgo{ + tls.CertCompressionBrotli, + }}, + &tls.ApplicationSettingsExtension{SupportedProtocols: []string{"h2"}}, + &tls.UtlsGREASEExtension{}, + &tls.UtlsPaddingExtension{GetPaddingLen: tls.BoringPaddingStyle}, + }, + } +} diff --git a/go.mod b/go.mod index d1f4e8d..e30c576 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/quic-go/quic-go v0.39.0 github.com/quic-go/webtransport-go v0.6.0 + github.com/refraction-networking/utls v1.5.4 github.com/rs/xid v1.3.0 github.com/shadowsocks/go-shadowsocks2 v0.1.5 github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 diff --git a/go.sum b/go.sum index f26f424..3ede771 100644 --- a/go.sum +++ b/go.sum @@ -292,6 +292,7 @@ github.com/quic-go/quic-go v0.39.0 h1:AgP40iThFMY0bj8jGxROhw3S0FMGa8ryqsmi9tBH3S github.com/quic-go/quic-go v0.39.0/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q= github.com/quic-go/webtransport-go v0.6.0 h1:CvNsKqc4W2HljHJnoT+rMmbRJybShZ0YPFDD3NxaZLY= github.com/quic-go/webtransport-go v0.6.0/go.mod h1:9KjU4AEBqEQidGHNDkZrb8CAa1abRaosM2yGOyiikEc= +github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= 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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=