mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-01-04 00:23:43 +08:00
feat: Support REALITY protocol
This commit is contained in:
parent
1e6f0f28f6
commit
76a8fe3839
41
adapter/outbound/reality.go
Normal file
41
adapter/outbound/reality.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package outbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RealityOptions struct {
|
||||||
|
ServerName string `proxy:"server-name"`
|
||||||
|
PublicKey string `proxy:"public-key"`
|
||||||
|
ShortID string `proxy:"short-id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
|
||||||
|
if o.PublicKey != "" || o.ServerName != "" {
|
||||||
|
if o.PublicKey != "" && o.ServerName != "" {
|
||||||
|
config := new(tlsC.RealityConfig)
|
||||||
|
|
||||||
|
n, err := base64.RawURLEncoding.Decode(config.PublicKey[:], []byte(o.PublicKey))
|
||||||
|
if err != nil || n != curve25519.ScalarSize {
|
||||||
|
return nil, errors.New("invalid REALITY public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.ShortID, err = hex.DecodeString(o.ShortID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid REALITY short ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.ServerName = o.ServerName
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid REALITY protocol option")
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -30,21 +30,22 @@ type Trojan struct {
|
|||||||
|
|
||||||
type TrojanOption struct {
|
type TrojanOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
Password string `proxy:"password"`
|
Password string `proxy:"password"`
|
||||||
ALPN []string `proxy:"alpn,omitempty"`
|
ALPN []string `proxy:"alpn,omitempty"`
|
||||||
SNI string `proxy:"sni,omitempty"`
|
SNI string `proxy:"sni,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
Flow string `proxy:"flow,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
FlowShow bool `proxy:"flow-show,omitempty"`
|
Flow string `proxy:"flow,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
FlowShow bool `proxy:"flow-show,omitempty"`
|
||||||
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
func (t *Trojan) plainStream(c net.Conn) (net.Conn, error) {
|
||||||
@ -244,6 +245,12 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||||||
tOption.ServerName = option.SNI
|
tOption.ServerName = option.SNI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
tOption.Reality, err = option.RealityOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
t := &Trojan{
|
t := &Trojan{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
@ -41,6 +41,8 @@ type Vless struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
|
realityConfig *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type VlessOption struct {
|
type VlessOption struct {
|
||||||
@ -57,6 +59,7 @@ type VlessOption struct {
|
|||||||
XUDP bool `proxy:"xudp,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
@ -78,7 +81,6 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &vmess.WebsocketConfig{
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -190,6 +192,7 @@ func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error)
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
FingerPrint: v.option.Fingerprint,
|
FingerPrint: v.option.Fingerprint,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if isH2 {
|
if isH2 {
|
||||||
@ -554,7 +557,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -35,32 +35,35 @@ type Vmess struct {
|
|||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
gunConfig *gun.Config
|
gunConfig *gun.Config
|
||||||
transport *gun.TransportWrap
|
transport *gun.TransportWrap
|
||||||
|
|
||||||
|
realityConfig *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type VmessOption struct {
|
type VmessOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
Server string `proxy:"server"`
|
Server string `proxy:"server"`
|
||||||
Port int `proxy:"port"`
|
Port int `proxy:"port"`
|
||||||
UUID string `proxy:"uuid"`
|
UUID string `proxy:"uuid"`
|
||||||
AlterID int `proxy:"alterId"`
|
AlterID int `proxy:"alterId"`
|
||||||
Cipher string `proxy:"cipher"`
|
Cipher string `proxy:"cipher"`
|
||||||
UDP bool `proxy:"udp,omitempty"`
|
UDP bool `proxy:"udp,omitempty"`
|
||||||
Network string `proxy:"network,omitempty"`
|
Network string `proxy:"network,omitempty"`
|
||||||
TLS bool `proxy:"tls,omitempty"`
|
TLS bool `proxy:"tls,omitempty"`
|
||||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||||
ServerName string `proxy:"servername,omitempty"`
|
ServerName string `proxy:"servername,omitempty"`
|
||||||
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||||
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"`
|
||||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"`
|
||||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||||
XUDP bool `proxy:"xudp,omitempty"`
|
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
XUDP bool `proxy:"xudp,omitempty"`
|
||||||
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||||
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
GlobalPadding bool `proxy:"global-padding,omitempty"`
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
AuthenticatedLength bool `proxy:"authenticated-length,omitempty"`
|
||||||
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPOptions struct {
|
type HTTPOptions struct {
|
||||||
@ -95,7 +98,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
|
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
wsOpts := &clashVMess.WebsocketConfig{
|
wsOpts := &clashVMess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
@ -144,12 +146,12 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
tlsOpts.Host = v.option.ServerName
|
tlsOpts.Host = v.option.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
c, err = clashVMess.StreamTLSConn(c, tlsOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -172,6 +174,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
NextProtos: []string{"h2"},
|
NextProtos: []string{"h2"},
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -199,6 +202,7 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|||||||
Host: host,
|
Host: host,
|
||||||
SkipCertVerify: v.option.SkipCertVerify,
|
SkipCertVerify: v.option.SkipCertVerify,
|
||||||
ClientFingerprint: v.option.ClientFingerprint,
|
ClientFingerprint: v.option.ClientFingerprint,
|
||||||
|
Reality: v.realityConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.option.ServerName != "" {
|
if v.option.ServerName != "" {
|
||||||
@ -452,8 +456,13 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
v.gunConfig = gunConfig
|
v.gunConfig = gunConfig
|
||||||
|
|
||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
163
component/tls/reality.go
Normal file
163
component/tls/reality.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/ed25519"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Dreamacro/clash/log"
|
||||||
|
|
||||||
|
utls "github.com/sagernet/utls"
|
||||||
|
"github.com/zhangyunhao116/fastrand"
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
"golang.org/x/crypto/hkdf"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RealityConfig struct {
|
||||||
|
ServerName string
|
||||||
|
PublicKey [curve25519.ScalarSize]byte
|
||||||
|
ShortID []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||||
|
if fingerprint, exists := GetFingerprint(ClientFingerprint); exists {
|
||||||
|
verifier := &realityVerifier{
|
||||||
|
serverName: realityConfig.ServerName,
|
||||||
|
}
|
||||||
|
uConfig := copyConfig(tlsConfig)
|
||||||
|
uConfig.ServerName = realityConfig.ServerName
|
||||||
|
uConfig.InsecureSkipVerify = true
|
||||||
|
uConfig.SessionTicketsDisabled = true
|
||||||
|
uConfig.VerifyPeerCertificate = verifier.VerifyPeerCertificate
|
||||||
|
clientID := utls.ClientHelloID{
|
||||||
|
Client: fingerprint.Client,
|
||||||
|
Version: fingerprint.Version,
|
||||||
|
Seed: fingerprint.Seed,
|
||||||
|
}
|
||||||
|
uConn := utls.UClient(conn, uConfig, clientID)
|
||||||
|
verifier.UConn = uConn
|
||||||
|
err := uConn.BuildHandshakeState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hello := uConn.HandshakeState.Hello
|
||||||
|
hello.SessionId = make([]byte, 32)
|
||||||
|
copy(hello.Raw[39:], hello.SessionId)
|
||||||
|
|
||||||
|
var nowTime time.Time
|
||||||
|
if uConfig.Time != nil {
|
||||||
|
nowTime = uConfig.Time()
|
||||||
|
} else {
|
||||||
|
nowTime = time.Now()
|
||||||
|
}
|
||||||
|
binary.BigEndian.PutUint64(hello.SessionId, uint64(nowTime.Unix()))
|
||||||
|
|
||||||
|
hello.SessionId[0] = 1
|
||||||
|
hello.SessionId[1] = 7
|
||||||
|
hello.SessionId[2] = 5
|
||||||
|
copy(hello.SessionId[8:], realityConfig.ShortID)
|
||||||
|
|
||||||
|
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
||||||
|
|
||||||
|
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(realityConfig.PublicKey[:])
|
||||||
|
if authKey == nil {
|
||||||
|
return nil, errors.New("nil auth_key")
|
||||||
|
}
|
||||||
|
verifier.authKey = authKey
|
||||||
|
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aesBlock, _ := aes.NewCipher(authKey)
|
||||||
|
aesGcmCipher, _ := cipher.NewGCM(aesBlock)
|
||||||
|
aesGcmCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
|
||||||
|
copy(hello.Raw[39:], hello.SessionId)
|
||||||
|
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)
|
||||||
|
//log.Debugln("REALITY uConn.AuthKey: %v", authKey)
|
||||||
|
|
||||||
|
err = uConn.HandshakeContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugln("REALITY Authentication: %v", verifier.verified)
|
||||||
|
|
||||||
|
if !verifier.verified {
|
||||||
|
go realityClientFallback(uConn, uConfig.ServerName, clientID)
|
||||||
|
return nil, errors.New("REALITY authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return uConn, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("unknown uTLS fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
|
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
|
||||||
|
defer uConn.Close()
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &http2.Transport{
|
||||||
|
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
|
||||||
|
return uConn, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
|
||||||
|
request.Header.Set("User-Agent", fingerprint.Client)
|
||||||
|
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", fastrand.Intn(32)+30)})
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//_, _ = io.Copy(io.Discard, response.Body)
|
||||||
|
time.Sleep(time.Duration(5 + fastrand.Int63n(10)))
|
||||||
|
response.Body.Close()
|
||||||
|
client.CloseIdleConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
type realityVerifier struct {
|
||||||
|
*utls.UConn
|
||||||
|
serverName string
|
||||||
|
authKey []byte
|
||||||
|
verified bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -30,7 +30,7 @@ require (
|
|||||||
github.com/sagernet/sing-shadowtls v0.1.0
|
github.com/sagernet/sing-shadowtls v0.1.0
|
||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc
|
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c
|
||||||
github.com/samber/lo v1.37.0
|
github.com/samber/lo v1.37.0
|
||||||
github.com/sirupsen/logrus v1.9.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
|
4
go.sum
4
go.sum
@ -135,8 +135,8 @@ github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc h1:vqlYWupvV
|
|||||||
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc/go.mod h1:V14iffGwhZPU2S7wgIiPlLWXygSjAXazYzD1w0ejBl4=
|
github.com/sagernet/sing-vmess v0.1.3-0.20230307060529-d110e81a50bc/go.mod h1:V14iffGwhZPU2S7wgIiPlLWXygSjAXazYzD1w0ejBl4=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d h1:trP/l6ZPWvQ/5Gv99Z7/t/v8iYy06akDMejxW1sznUk=
|
||||||
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
github.com/sagernet/tfo-go v0.0.0-20230207095944-549363a7327d/go.mod h1:jk6Ii8Y3En+j2KQDLgdgQGwb3M6y7EL567jFnGYhN9g=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056 h1:gDXi/0uYe8dA48UyUI1LM2la5QYN0IvsDvR2H2+kFnA=
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01 h1:m4MI13+NRKddIvbdSN0sFHK8w5ROTa60Zi9diZ7EE08=
|
||||||
github.com/sagernet/utls v0.0.0-20230220130002-c08891932056/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
github.com/sagernet/utls v0.0.0-20230225061716-536a007c8b01/go.mod h1:JKQMZq/O2qnZjdrt+B57olmfgEmLtY9iiSIEYtWvoSM=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c h1:vK2wyt9aWYHHvNLWniwijBu/n4pySypiKRhN32u/JGo=
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
github.com/sagernet/wireguard-go v0.0.0-20221116151939-c99467f53f2c/go.mod h1:euOmN6O5kk9dQmgSS8Df4psAl3TCjxOz0NW60EWkSaI=
|
||||||
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/Dreamacro/clash/transport/socks5"
|
"github.com/Dreamacro/clash/transport/socks5"
|
||||||
"github.com/Dreamacro/clash/transport/vless"
|
"github.com/Dreamacro/clash/transport/vless"
|
||||||
"github.com/Dreamacro/clash/transport/vmess"
|
"github.com/Dreamacro/clash/transport/vmess"
|
||||||
|
|
||||||
xtls "github.com/xtls/go"
|
xtls "github.com/xtls/go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ type Option struct {
|
|||||||
Flow string
|
Flow string
|
||||||
FlowShow bool
|
FlowShow bool
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
|
Reality *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebsocketOption struct {
|
type WebsocketOption struct {
|
||||||
@ -117,16 +119,24 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(t.option.ClientFingerprint) != 0 {
|
if len(t.option.ClientFingerprint) != 0 {
|
||||||
utlsConn, valid := vmess.GetUtlsConnWithClientFingerprint(conn, t.option.ClientFingerprint, tlsConfig)
|
if t.option.Reality == nil {
|
||||||
if valid {
|
utlsConn, valid := vmess.GetUTLSConn(conn, t.option.ClientFingerprint, tlsConfig)
|
||||||
|
if valid {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||||
|
return utlsConn, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
return tlsC.GetRealityConn(ctx, conn, t.option.ClientFingerprint, tlsConfig, t.option.Reality)
|
||||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
|
||||||
return utlsConn, err
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if t.option.Reality != nil {
|
||||||
|
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package vmess
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
tlsC "github.com/Dreamacro/clash/component/tls"
|
tlsC "github.com/Dreamacro/clash/component/tls"
|
||||||
@ -15,6 +16,7 @@ type TLSConfig struct {
|
|||||||
FingerPrint string
|
FingerPrint string
|
||||||
ClientFingerprint string
|
ClientFingerprint string
|
||||||
NextProtos []string
|
NextProtos []string
|
||||||
|
Reality *tlsC.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
||||||
@ -34,15 +36,25 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.ClientFingerprint) != 0 {
|
if len(cfg.ClientFingerprint) != 0 {
|
||||||
utlsConn, valid := GetUtlsConnWithClientFingerprint(conn, cfg.ClientFingerprint, tlsConfig)
|
if cfg.Reality == nil {
|
||||||
if valid {
|
utlsConn, valid := GetUTLSConn(conn, cfg.ClientFingerprint, tlsConfig)
|
||||||
|
if valid {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||||
|
return utlsConn, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
return tlsC.GetRealityConn(ctx, conn, cfg.ClientFingerprint, tlsConfig, cfg.Reality)
|
||||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
|
||||||
return utlsConn, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if cfg.Reality != nil {
|
||||||
|
return nil, errors.New("REALITY is based on uTLS, please set a client-fingerprint")
|
||||||
|
}
|
||||||
|
|
||||||
tlsConn := tls.Client(conn, tlsConfig)
|
tlsConn := tls.Client(conn, tlsConfig)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||||
@ -52,7 +64,7 @@ func StreamTLSConn(conn net.Conn, cfg *TLSConfig) (net.Conn, error) {
|
|||||||
return tlsConn, err
|
return tlsConn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUtlsConnWithClientFingerprint(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) {
|
func GetUTLSConn(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) {
|
||||||
|
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
|
if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
|
||||||
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user