github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/transport/internet/reality/reality.go (about)

     1  package reality
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/aes"
     7  	"crypto/cipher"
     8  	"crypto/ecdh"
     9  	"crypto/ed25519"
    10  	"crypto/hmac"
    11  	"crypto/rand"
    12  	"crypto/sha256"
    13  	"crypto/sha512"
    14  	gotls "crypto/tls"
    15  	"crypto/x509"
    16  	"encoding/binary"
    17  	"fmt"
    18  	"io"
    19  	"math/big"
    20  	"net/http"
    21  	"reflect"
    22  	"regexp"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  	"unsafe"
    27  
    28  	utls "github.com/refraction-networking/utls"
    29  	"github.com/xtls/reality"
    30  	"github.com/xmplusdev/xmcore/common/errors"
    31  	"github.com/xmplusdev/xmcore/common/net"
    32  	"github.com/xmplusdev/xmcore/common/session"
    33  	"github.com/xmplusdev/xmcore/core"
    34  	"github.com/xmplusdev/xmcore/transport/internet/tls"
    35  	"golang.org/x/crypto/chacha20poly1305"
    36  	"golang.org/x/crypto/hkdf"
    37  	"golang.org/x/net/http2"
    38  )
    39  
    40  //go:generate go run github.com/xmplusdev/xmcore/common/errors/errorgen
    41  
    42  //go:linkname aesgcmPreferred github.com/refraction-networking/utls.aesgcmPreferred
    43  func aesgcmPreferred(ciphers []uint16) bool
    44  
    45  type Conn struct {
    46  	*reality.Conn
    47  }
    48  
    49  func (c *Conn) HandshakeAddress() net.Address {
    50  	if err := c.Handshake(); err != nil {
    51  		return nil
    52  	}
    53  	state := c.ConnectionState()
    54  	if state.ServerName == "" {
    55  		return nil
    56  	}
    57  	return net.ParseAddress(state.ServerName)
    58  }
    59  
    60  func Server(c net.Conn, config *reality.Config) (net.Conn, error) {
    61  	realityConn, err := reality.Server(context.Background(), c, config)
    62  	return &Conn{Conn: realityConn}, err
    63  }
    64  
    65  type UConn struct {
    66  	*utls.UConn
    67  	ServerName string
    68  	AuthKey    []byte
    69  	Verified   bool
    70  }
    71  
    72  func (c *UConn) HandshakeAddress() net.Address {
    73  	if err := c.Handshake(); err != nil {
    74  		return nil
    75  	}
    76  	state := c.ConnectionState()
    77  	if state.ServerName == "" {
    78  		return nil
    79  	}
    80  	return net.ParseAddress(state.ServerName)
    81  }
    82  
    83  func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
    84  	p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
    85  	certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
    86  	if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
    87  		h := hmac.New(sha512.New, c.AuthKey)
    88  		h.Write(pub)
    89  		if bytes.Equal(h.Sum(nil), certs[0].Signature) {
    90  			c.Verified = true
    91  			return nil
    92  		}
    93  	}
    94  	opts := x509.VerifyOptions{
    95  		DNSName:       c.ServerName,
    96  		Intermediates: x509.NewCertPool(),
    97  	}
    98  	for _, cert := range certs[1:] {
    99  		opts.Intermediates.AddCert(cert)
   100  	}
   101  	if _, err := certs[0].Verify(opts); err != nil {
   102  		return err
   103  	}
   104  	return nil
   105  }
   106  
   107  func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) {
   108  	localAddr := c.LocalAddr().String()
   109  	uConn := &UConn{}
   110  	utlsConfig := &utls.Config{
   111  		VerifyPeerCertificate:  uConn.VerifyPeerCertificate,
   112  		ServerName:             config.ServerName,
   113  		InsecureSkipVerify:     true,
   114  		SessionTicketsDisabled: true,
   115  		KeyLogWriter:           KeyLogWriterFromConfig(config),
   116  	}
   117  	if utlsConfig.ServerName == "" {
   118  		utlsConfig.ServerName = dest.Address.String()
   119  	} else if strings.ToLower(utlsConfig.ServerName) == "nosni" { // If ServerName is set to "nosni", we set it empty.
   120  		utlsConfig.ServerName = ""
   121  	}
   122  	uConn.ServerName = utlsConfig.ServerName
   123  	fingerprint := tls.GetFingerprint(config.Fingerprint)
   124  	if fingerprint == nil {
   125  		return nil, newError("REALITY: failed to get fingerprint").AtError()
   126  	}
   127  	uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint)
   128  	{
   129  		uConn.BuildHandshakeState()
   130  		hello := uConn.HandshakeState.Hello
   131  		hello.SessionId = make([]byte, 32)
   132  		copy(hello.Raw[39:], hello.SessionId) // the fixed location of `Session ID`
   133  		hello.SessionId[0] = core.Version_x
   134  		hello.SessionId[1] = core.Version_y
   135  		hello.SessionId[2] = core.Version_z
   136  		hello.SessionId[3] = 0 // reserved
   137  		binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
   138  		copy(hello.SessionId[8:], config.ShortId)
   139  		if config.Show {
   140  			newError(fmt.Sprintf("REALITY localAddr: %v\thello.SessionId[:16]: %v\n", localAddr, hello.SessionId[:16])).WriteToLog(session.ExportIDToError(ctx))
   141  		}
   142  		publicKey, err := ecdh.X25519().NewPublicKey(config.PublicKey)
   143  		if err != nil {
   144  			return nil, errors.New("REALITY: publicKey == nil")
   145  		}
   146  		uConn.AuthKey, _ = uConn.HandshakeState.State13.EcdheKey.ECDH(publicKey)
   147  		if uConn.AuthKey == nil {
   148  			return nil, errors.New("REALITY: SharedKey == nil")
   149  		}
   150  		if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil {
   151  			return nil, err
   152  		}
   153  		var aead cipher.AEAD
   154  		if aesgcmPreferred(hello.CipherSuites) {
   155  			block, _ := aes.NewCipher(uConn.AuthKey)
   156  			aead, _ = cipher.NewGCM(block)
   157  		} else {
   158  			aead, _ = chacha20poly1305.New(uConn.AuthKey)
   159  		}
   160  		if config.Show {
   161  			newError(fmt.Sprintf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead)).WriteToLog(session.ExportIDToError(ctx))
   162  		}
   163  		aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
   164  		copy(hello.Raw[39:], hello.SessionId)
   165  	}
   166  	if err := uConn.HandshakeContext(ctx); err != nil {
   167  		return nil, err
   168  	}
   169  	if config.Show {
   170  		newError(fmt.Sprintf("REALITY localAddr: %v\tuConn.Verified: %v\n", localAddr, uConn.Verified)).WriteToLog(session.ExportIDToError(ctx))
   171  	}
   172  	if !uConn.Verified {
   173  		go func() {
   174  			client := &http.Client{
   175  				Transport: &http2.Transport{
   176  					DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) {
   177  						newError(fmt.Sprintf("REALITY localAddr: %v\tDialTLSContext\n", localAddr)).WriteToLog(session.ExportIDToError(ctx))
   178  						return uConn, nil
   179  					},
   180  				},
   181  			}
   182  			prefix := []byte("https://" + uConn.ServerName)
   183  			maps.Lock()
   184  			if maps.maps == nil {
   185  				maps.maps = make(map[string]map[string]bool)
   186  			}
   187  			paths := maps.maps[uConn.ServerName]
   188  			if paths == nil {
   189  				paths = make(map[string]bool)
   190  				paths[config.SpiderX] = true
   191  				maps.maps[uConn.ServerName] = paths
   192  			}
   193  			firstURL := string(prefix) + getPathLocked(paths)
   194  			maps.Unlock()
   195  			get := func(first bool) {
   196  				var (
   197  					req  *http.Request
   198  					resp *http.Response
   199  					err  error
   200  					body []byte
   201  				)
   202  				if first {
   203  					req, _ = http.NewRequest("GET", firstURL, nil)
   204  				} else {
   205  					maps.Lock()
   206  					req, _ = http.NewRequest("GET", string(prefix)+getPathLocked(paths), nil)
   207  					maps.Unlock()
   208  				}
   209  				req.Header.Set("User-Agent", fingerprint.Client) // TODO: User-Agent map
   210  				if first && config.Show {
   211  					newError(fmt.Sprintf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent())).WriteToLog(session.ExportIDToError(ctx))
   212  				}
   213  				times := 1
   214  				if !first {
   215  					times = int(randBetween(config.SpiderY[4], config.SpiderY[5]))
   216  				}
   217  				for j := 0; j < times; j++ {
   218  					if !first && j == 0 {
   219  						req.Header.Set("Referer", firstURL)
   220  					}
   221  					req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(randBetween(config.SpiderY[0], config.SpiderY[1])))})
   222  					if resp, err = client.Do(req); err != nil {
   223  						break
   224  					}
   225  					req.Header.Set("Referer", req.URL.String())
   226  					if body, err = io.ReadAll(resp.Body); err != nil {
   227  						break
   228  					}
   229  					maps.Lock()
   230  					for _, m := range href.FindAllSubmatch(body, -1) {
   231  						m[1] = bytes.TrimPrefix(m[1], prefix)
   232  						if !bytes.Contains(m[1], dot) {
   233  							paths[string(m[1])] = true
   234  						}
   235  					}
   236  					req.URL.Path = getPathLocked(paths)
   237  					if config.Show {
   238  						newError(fmt.Sprintf("REALITY localAddr: %v\treq.Referer(): %v\n", localAddr, req.Referer())).WriteToLog(session.ExportIDToError(ctx))
   239  						newError(fmt.Sprintf("REALITY localAddr: %v\tlen(body): %v\n", localAddr, len(body))).WriteToLog(session.ExportIDToError(ctx))
   240  						newError(fmt.Sprintf("REALITY localAddr: %v\tlen(paths): %v\n", localAddr, len(paths))).WriteToLog(session.ExportIDToError(ctx))
   241  					}
   242  					maps.Unlock()
   243  					if !first {
   244  						time.Sleep(time.Duration(randBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
   245  					}
   246  				}
   247  			}
   248  			get(true)
   249  			concurrency := int(randBetween(config.SpiderY[2], config.SpiderY[3]))
   250  			for i := 0; i < concurrency; i++ {
   251  				go get(false)
   252  			}
   253  			// Do not close the connection
   254  		}()
   255  		time.Sleep(time.Duration(randBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
   256  		return nil, errors.New("REALITY: processed invalid connection")
   257  	}
   258  	return uConn, nil
   259  }
   260  
   261  var (
   262  	href = regexp.MustCompile(`href="([/h].*?)"`)
   263  	dot  = []byte(".")
   264  )
   265  
   266  var maps struct {
   267  	sync.Mutex
   268  	maps map[string]map[string]bool
   269  }
   270  
   271  func getPathLocked(paths map[string]bool) string {
   272  	stopAt := int(randBetween(0, int64(len(paths)-1)))
   273  	i := 0
   274  	for s := range paths {
   275  		if i == stopAt {
   276  			return s
   277  		}
   278  		i++
   279  	}
   280  	return "/"
   281  }
   282  
   283  func randBetween(left int64, right int64) int64 {
   284  	if left == right {
   285  		return left
   286  	}
   287  	bigInt, _ := rand.Int(rand.Reader, big.NewInt(right-left))
   288  	return left + bigInt.Int64()
   289  }