You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
8.8 KiB
299 lines
8.8 KiB
package reality |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"crypto/ecdh" |
|
"crypto/ed25519" |
|
"crypto/hmac" |
|
"crypto/sha256" |
|
"crypto/sha512" |
|
gotls "crypto/tls" |
|
"crypto/x509" |
|
"encoding/binary" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"reflect" |
|
"regexp" |
|
"strings" |
|
"sync" |
|
"time" |
|
"unsafe" |
|
|
|
"github.com/cloudflare/circl/sign/mldsa/mldsa65" |
|
utls "github.com/refraction-networking/utls" |
|
"github.com/xtls/reality" |
|
"github.com/xtls/xray-core/common/crypto" |
|
"github.com/xtls/xray-core/common/errors" |
|
"github.com/xtls/xray-core/common/net" |
|
"github.com/xtls/xray-core/common/utils" |
|
"github.com/xtls/xray-core/core" |
|
"github.com/xtls/xray-core/transport/internet/tls" |
|
"golang.org/x/crypto/hkdf" |
|
"golang.org/x/net/http2" |
|
) |
|
|
|
type Conn struct { |
|
*reality.Conn |
|
} |
|
|
|
func (c *Conn) HandshakeAddress() net.Address { |
|
if err := c.Handshake(); err != nil { |
|
return nil |
|
} |
|
state := c.ConnectionState() |
|
if state.ServerName == "" { |
|
return nil |
|
} |
|
return net.ParseAddress(state.ServerName) |
|
} |
|
|
|
func Server(c net.Conn, config *reality.Config) (net.Conn, error) { |
|
realityConn, err := reality.Server(context.Background(), c, config) |
|
return &Conn{Conn: realityConn}, err |
|
} |
|
|
|
type UConn struct { |
|
*utls.UConn |
|
Config *Config |
|
ServerName string |
|
AuthKey []byte |
|
Verified bool |
|
} |
|
|
|
func (c *UConn) HandshakeAddress() net.Address { |
|
if err := c.Handshake(); err != nil { |
|
return nil |
|
} |
|
state := c.ConnectionState() |
|
if state.ServerName == "" { |
|
return nil |
|
} |
|
return net.ParseAddress(state.ServerName) |
|
} |
|
|
|
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { |
|
if c.Config.Show { |
|
localAddr := c.LocalAddr().String() |
|
fmt.Printf("REALITY localAddr: %v\tis using X25519MLKEM768 for TLS' communication: %v\n", localAddr, c.HandshakeState.ServerHello.ServerShare.Group == utls.X25519MLKEM768) |
|
fmt.Printf("REALITY localAddr: %v\tis using ML-DSA-65 for cert's extra verification: %v\n", localAddr, len(c.Config.Mldsa65Verify) > 0) |
|
} |
|
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates") |
|
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset)) |
|
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok { |
|
h := hmac.New(sha512.New, c.AuthKey) |
|
h.Write(pub) |
|
if bytes.Equal(h.Sum(nil), certs[0].Signature) { |
|
if len(c.Config.Mldsa65Verify) > 0 { |
|
if len(certs[0].Extensions) > 0 { |
|
h.Write(c.HandshakeState.Hello.Raw) |
|
h.Write(c.HandshakeState.ServerHello.Raw) |
|
verify, _ := mldsa65.Scheme().UnmarshalBinaryPublicKey(c.Config.Mldsa65Verify) |
|
if mldsa65.Verify(verify.(*mldsa65.PublicKey), h.Sum(nil), nil, certs[0].Extensions[0].Value) { |
|
c.Verified = true |
|
return nil |
|
} |
|
} |
|
} else { |
|
c.Verified = true |
|
return nil |
|
} |
|
} |
|
} |
|
opts := x509.VerifyOptions{ |
|
DNSName: c.ServerName, |
|
Intermediates: x509.NewCertPool(), |
|
} |
|
for _, cert := range certs[1:] { |
|
opts.Intermediates.AddCert(cert) |
|
} |
|
if _, err := certs[0].Verify(opts); err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) { |
|
localAddr := c.LocalAddr().String() |
|
uConn := &UConn{ |
|
Config: config, |
|
} |
|
utlsConfig := &utls.Config{ |
|
VerifyPeerCertificate: uConn.VerifyPeerCertificate, |
|
ServerName: config.ServerName, |
|
InsecureSkipVerify: true, |
|
SessionTicketsDisabled: true, |
|
KeyLogWriter: KeyLogWriterFromConfig(config), |
|
} |
|
if utlsConfig.ServerName == "" { |
|
utlsConfig.ServerName = dest.Address.String() |
|
} |
|
uConn.ServerName = utlsConfig.ServerName |
|
fingerprint := tls.GetFingerprint(config.Fingerprint) |
|
if fingerprint == nil { |
|
return nil, errors.New("REALITY: failed to get fingerprint").AtError() |
|
} |
|
uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint) |
|
{ |
|
uConn.BuildHandshakeState() |
|
hello := uConn.HandshakeState.Hello |
|
hello.SessionId = make([]byte, 32) |
|
copy(hello.Raw[39:], hello.SessionId) // the fixed location of `Session ID` |
|
hello.SessionId[0] = core.Version_x |
|
hello.SessionId[1] = core.Version_y |
|
hello.SessionId[2] = core.Version_z |
|
hello.SessionId[3] = 0 // reserved |
|
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix())) |
|
copy(hello.SessionId[8:], config.ShortId) |
|
if config.Show { |
|
fmt.Printf("REALITY localAddr: %v\thello.SessionId[:16]: %v\n", localAddr, hello.SessionId[:16]) |
|
} |
|
publicKey, err := ecdh.X25519().NewPublicKey(config.PublicKey) |
|
if err != nil { |
|
return nil, errors.New("REALITY: publicKey == nil") |
|
} |
|
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe |
|
if ecdhe == nil { |
|
ecdhe = uConn.HandshakeState.State13.KeyShareKeys.MlkemEcdhe |
|
} |
|
if ecdhe == nil { |
|
return nil, errors.New("Current fingerprint ", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, " does not support TLS 1.3, REALITY handshake cannot establish.") |
|
} |
|
uConn.AuthKey, _ = ecdhe.ECDH(publicKey) |
|
if uConn.AuthKey == nil { |
|
return nil, errors.New("REALITY: SharedKey == nil") |
|
} |
|
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil { |
|
return nil, err |
|
} |
|
aead := crypto.NewAesGcm(uConn.AuthKey) |
|
if config.Show { |
|
fmt.Printf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead) |
|
} |
|
aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw) |
|
copy(hello.Raw[39:], hello.SessionId) |
|
} |
|
if err := uConn.HandshakeContext(ctx); err != nil { |
|
return nil, err |
|
} |
|
if config.Show { |
|
fmt.Printf("REALITY localAddr: %v\tuConn.Verified: %v\n", localAddr, uConn.Verified) |
|
} |
|
if !uConn.Verified { |
|
errors.LogError(ctx, "REALITY: received real certificate (potential MITM or redirection)") |
|
go func() { |
|
client := &http.Client{ |
|
Transport: &http2.Transport{ |
|
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) { |
|
if config.Show { |
|
fmt.Printf("REALITY localAddr: %v\tDialTLSContext\n", localAddr) |
|
} |
|
return uConn, nil |
|
}, |
|
}, |
|
} |
|
prefix := []byte("https://" + uConn.ServerName) |
|
maps.Lock() |
|
if maps.maps == nil { |
|
maps.maps = make(map[string]map[string]struct{}) |
|
} |
|
paths := maps.maps[uConn.ServerName] |
|
if paths == nil { |
|
paths = make(map[string]struct{}) |
|
paths[config.SpiderX] = struct{}{} |
|
maps.maps[uConn.ServerName] = paths |
|
} |
|
firstURL := string(prefix) + getPathLocked(paths) |
|
maps.Unlock() |
|
get := func(first bool) { |
|
var ( |
|
req *http.Request |
|
resp *http.Response |
|
err error |
|
body []byte |
|
) |
|
if first { |
|
req, _ = http.NewRequest("GET", firstURL, nil) |
|
} else { |
|
maps.Lock() |
|
req, _ = http.NewRequest("GET", string(prefix)+getPathLocked(paths), nil) |
|
maps.Unlock() |
|
} |
|
if req == nil { |
|
return |
|
} |
|
utils.TryDefaultHeadersWith(req.Header, "nav") |
|
if first && config.Show { |
|
fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent()) |
|
} |
|
times := 1 |
|
if !first { |
|
times = int(crypto.RandBetween(config.SpiderY[4], config.SpiderY[5])) |
|
} |
|
for j := 0; j < times; j++ { |
|
if !first && j == 0 { |
|
req.Header.Set("Referer", firstURL) |
|
} |
|
req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(crypto.RandBetween(config.SpiderY[0], config.SpiderY[1])))}) |
|
if resp, err = client.Do(req); err != nil { |
|
break |
|
} |
|
defer resp.Body.Close() |
|
req.Header.Set("Referer", req.URL.String()) |
|
if body, err = io.ReadAll(resp.Body); err != nil { |
|
break |
|
} |
|
maps.Lock() |
|
for _, m := range href.FindAllSubmatch(body, -1) { |
|
m[1] = bytes.TrimPrefix(m[1], prefix) |
|
if !bytes.Contains(m[1], dot) { |
|
paths[string(m[1])] = struct{}{} |
|
} |
|
} |
|
req.URL.Path = getPathLocked(paths) |
|
if config.Show { |
|
fmt.Printf("REALITY localAddr: %v\treq.Referer(): %v\n", localAddr, req.Referer()) |
|
fmt.Printf("REALITY localAddr: %v\tlen(body): %v\n", localAddr, len(body)) |
|
fmt.Printf("REALITY localAddr: %v\tlen(paths): %v\n", localAddr, len(paths)) |
|
} |
|
maps.Unlock() |
|
if !first { |
|
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval |
|
} |
|
} |
|
} |
|
get(true) |
|
concurrency := int(crypto.RandBetween(config.SpiderY[2], config.SpiderY[3])) |
|
for i := 0; i < concurrency; i++ { |
|
go get(false) |
|
} |
|
// Do not close the connection |
|
}() |
|
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return |
|
return nil, errors.New("REALITY: processed invalid connection").AtWarning() |
|
} |
|
return uConn, nil |
|
} |
|
|
|
var ( |
|
href = regexp.MustCompile(`href="([/h].*?)"`) |
|
dot = []byte(".") |
|
) |
|
|
|
var maps struct { |
|
sync.Mutex |
|
maps map[string]map[string]struct{} |
|
} |
|
|
|
func getPathLocked(paths map[string]struct{}) string { |
|
stopAt := int(crypto.RandBetween(0, int64(len(paths)-1))) |
|
i := 0 |
|
for s := range paths { |
|
if i == stopAt { |
|
return s |
|
} |
|
i++ |
|
} |
|
return "/" |
|
}
|
|
|