mirror of
https://github.com/bolucat/Archive.git
synced 2025-09-26 20:21:35 +08:00
Update On Sun Aug 31 20:34:09 CEST 2025
This commit is contained in:
@@ -610,7 +610,6 @@ proxies: # socks5
|
||||
uuid: uuid
|
||||
network: tcp
|
||||
servername: example.com # AKA SNI
|
||||
# flow: xtls-rprx-direct # xtls-rprx-origin # enable XTLS
|
||||
# skip-cert-verify: true
|
||||
# fingerprint: xxxx
|
||||
# client-fingerprint: random # Available: "chrome","firefox","safari","random","none"
|
||||
@@ -640,10 +639,16 @@ proxies: # socks5
|
||||
network: tcp
|
||||
# -------------------------
|
||||
# vless encryption客户端配置:
|
||||
# (native/xorpub 的 XTLS 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
|
||||
# (native/xorpub 的 XTLS Vision 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
#
|
||||
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333":
|
||||
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
|
||||
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to")
|
||||
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding(若为 0 则不 Write())
|
||||
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
|
||||
# -------------------------
|
||||
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(X25519 Password).(ML-KEM-768 Client)..."
|
||||
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(padding len).(padding gap).(X25519 Password).(ML-KEM-768 Client)..."
|
||||
tls: false #可以不开启tls
|
||||
udp: true
|
||||
|
||||
@@ -1367,8 +1372,14 @@ listeners:
|
||||
# vless encryption服务端配置:
|
||||
# (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式)
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
#
|
||||
# Padding 是可选的参数,仅作用于 1-RTT 以消除握手的长度特征,双端默认值均为 "100-111-1111.75-0-111.50-0-3333":
|
||||
# 在 1-RTT client/server hello 后以 100% 的概率粘上随机 111 到 1111 字节的 padding
|
||||
# 以 75% 的概率等待随机 0 到 111 毫秒("probability-from-to")
|
||||
# 再次以 50% 的概率发送随机 0 到 3333 字节的 padding(若为 0 则不 Write())
|
||||
# 服务端、客户端可以设置不同的 padding 参数,按 len、gap 的顺序无限串联,第一个 padding 需概率 100%、至少 35 字节
|
||||
# -------------------------
|
||||
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(X25519 PrivateKey).(ML-KEM-768 Seed)..."
|
||||
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(padding len).(padding gap).(X25519 PrivateKey).(ML-KEM-768 Seed)..."
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
|
@@ -99,6 +99,15 @@ func TestInboundVless_Encryption(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
paddings := []struct {
|
||||
name string
|
||||
data string
|
||||
}{
|
||||
{"unconfigured-padding", ""},
|
||||
{"default-padding", "100-111-1111.75-0-111.50-0-3333."},
|
||||
{"old-padding", "100-100-1000."}, // Xray-core v25.8.29
|
||||
{"custom-padding", "100-1234-7890.33-0-1111.66-0-6666.55-111-777."},
|
||||
}
|
||||
var modes = []string{
|
||||
"native",
|
||||
"xorpub",
|
||||
@@ -107,19 +116,26 @@ func TestInboundVless_Encryption(t *testing.T) {
|
||||
for i := range modes {
|
||||
mode := modes[i]
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64,
|
||||
t.Parallel()
|
||||
for i := range paddings {
|
||||
padding := paddings[i].data
|
||||
t.Run(paddings[i].name, func(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Decryption: "mlkem768x25519plus." + mode + ".600s." + padding + privateKeyBase64 + "." + seedBase64,
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + padding + passwordBase64 + "." + clientBase64,
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
})
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + passwordBase64 + "." + clientBase64,
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -230,7 +230,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
|
||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||
if l.decryption != nil {
|
||||
var err error
|
||||
conn, err = l.decryption.Handshake(conn)
|
||||
conn, err = l.decryption.Handshake(conn, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@@ -87,7 +87,11 @@ func (s *serverHandler) handle() {
|
||||
_ = s.handleMessage()
|
||||
}()
|
||||
|
||||
<-s.quicConn.HandshakeComplete()
|
||||
select {
|
||||
case <-s.quicConn.HandshakeComplete(): // this chan maybe not closed if handshake never complete
|
||||
case <-time.After(s.quicConn.Config().HandshakeIdleTimeout): // HandshakeIdleTimeout in real conn.Config() never be zero
|
||||
}
|
||||
|
||||
time.AfterFunc(s.AuthenticationTimeout, func() {
|
||||
if s.v4Handler != nil {
|
||||
if s.v4Handler.AuthOk() {
|
||||
|
@@ -7,11 +7,23 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/utls/mlkem"
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
var (
|
||||
// Keep in sync with crypto/tls/cipher_suites.go.
|
||||
hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3
|
||||
hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
|
||||
hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH
|
||||
hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le"
|
||||
|
||||
HasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64
|
||||
)
|
||||
|
||||
type ClientInstance struct {
|
||||
@@ -21,6 +33,8 @@ type ClientInstance struct {
|
||||
RelaysLength int
|
||||
XorMode uint32
|
||||
Seconds uint32
|
||||
PaddingLens [][3]int
|
||||
PaddingGaps [][3]int
|
||||
|
||||
RWLock sync.RWMutex
|
||||
Expire time.Time
|
||||
@@ -28,15 +42,13 @@ type ClientInstance struct {
|
||||
Ticket []byte
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
|
||||
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
|
||||
if i.NfsPKeys != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
l := len(nfsPKeysBytes)
|
||||
if l == 0 {
|
||||
err = errors.New("empty nfsPKeysBytes")
|
||||
return
|
||||
return errors.New("empty nfsPKeysBytes")
|
||||
}
|
||||
i.NfsPKeys = make([]any, l)
|
||||
i.NfsPKeysBytes = nfsPKeysBytes
|
||||
@@ -58,18 +70,18 @@ func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32) (
|
||||
i.RelaysLength -= 32
|
||||
i.XorMode = xorMode
|
||||
i.Seconds = seconds
|
||||
return
|
||||
return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if i.NfsPKeys == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
c := NewCommonConn(conn)
|
||||
c := NewCommonConn(conn, HasAESGCMHardwareSupport)
|
||||
|
||||
ivAndRealysLength := 16 + i.RelaysLength
|
||||
pfsKeyExchangeLength := 18 + 1184 + 32 + 16
|
||||
paddingLength := int(randBetween(100, 1000))
|
||||
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
|
||||
clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)
|
||||
|
||||
iv := clientHello[:16]
|
||||
@@ -107,18 +119,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])
|
||||
relays = relays[index+32:]
|
||||
}
|
||||
nfsGCM := NewGCM(iv, nfsKey)
|
||||
nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
|
||||
|
||||
if i.Seconds > 0 {
|
||||
i.RWLock.RLock()
|
||||
if time.Now().Before(i.Expire) {
|
||||
c.Client = i
|
||||
c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection
|
||||
nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
|
||||
nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
|
||||
nfsAEAD.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil)
|
||||
nfsAEAD.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil)
|
||||
i.RWLock.RUnlock()
|
||||
c.PreWrite = clientHello[:ivAndRealysLength+18+32]
|
||||
c.GCM = NewGCM(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey)
|
||||
c.AEAD = NewAEAD(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey, c.UseAES)
|
||||
if i.XorMode == 2 {
|
||||
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16)
|
||||
}
|
||||
@@ -128,26 +140,34 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
}
|
||||
|
||||
pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]
|
||||
nfsGCM.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
|
||||
nfsAEAD.Seal(pfsKeyExchange[:0], nil, EncodeLength(pfsKeyExchangeLength-18), nil)
|
||||
mlkem768DKey, _ := mlkem.GenerateKey768()
|
||||
x25519SKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
pfsPublicKey := append(mlkem768DKey.EncapsulationKey().Bytes(), x25519SKey.PublicKey().Bytes()...)
|
||||
nfsGCM.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
|
||||
nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
|
||||
|
||||
padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]
|
||||
nfsGCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
nfsGCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
|
||||
if _, err := conn.Write(clientHello); err != nil {
|
||||
return nil, err
|
||||
paddingLens[0] = ivAndRealysLength + pfsKeyExchangeLength + paddingLens[0]
|
||||
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
if l > 0 {
|
||||
if _, err := conn.Write(clientHello[:l]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientHello = clientHello[l:]
|
||||
}
|
||||
if len(paddingGaps) > i {
|
||||
time.Sleep(paddingGaps[i])
|
||||
}
|
||||
}
|
||||
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
|
||||
encryptedPfsPublicKey := make([]byte, 1088+32+16)
|
||||
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nfsGCM.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
|
||||
nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
|
||||
mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -164,14 +184,14 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
copy(pfsKey, mlkem768Key)
|
||||
copy(pfsKey[32:], x25519Key)
|
||||
c.UnitedKey = append(pfsKey, nfsKey...)
|
||||
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey)
|
||||
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1088+32], c.UnitedKey)
|
||||
c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
|
||||
c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1088+32], c.UnitedKey, c.UseAES)
|
||||
|
||||
encryptedTicket := make([]byte, 32)
|
||||
if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
|
||||
if _, err := c.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seconds := DecodeLength(encryptedTicket)
|
||||
@@ -188,7 +208,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
if _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
length := DecodeLength(encryptedLength[:2])
|
||||
|
@@ -8,29 +8,34 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
|
||||
"github.com/metacubex/blake3"
|
||||
"github.com/metacubex/randv2"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
type CommonConn struct {
|
||||
net.Conn
|
||||
UseAES bool
|
||||
Client *ClientInstance
|
||||
UnitedKey []byte
|
||||
PreWrite []byte
|
||||
GCM *GCM
|
||||
AEAD *AEAD
|
||||
PeerAEAD *AEAD
|
||||
PeerPadding []byte
|
||||
rawInput bytes.Buffer // PeerInBytes
|
||||
PeerGCM *GCM
|
||||
input bytes.Reader // PeerCache
|
||||
}
|
||||
|
||||
func NewCommonConn(conn net.Conn) *CommonConn {
|
||||
func NewCommonConn(conn net.Conn, useAES bool) *CommonConn {
|
||||
return &CommonConn{
|
||||
Conn: conn,
|
||||
Conn: conn,
|
||||
UseAES: useAES,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,12 +54,12 @@ func (c *CommonConn) Write(b []byte) (int, error) {
|
||||
headerAndData := outBytes[:5+len(b)+16]
|
||||
EncodeHeader(headerAndData, len(b)+16)
|
||||
max := false
|
||||
if bytes.Equal(c.GCM.Nonce[:], MaxNonce) {
|
||||
if bytes.Equal(c.AEAD.Nonce[:], MaxNonce) {
|
||||
max = true
|
||||
}
|
||||
c.GCM.Seal(headerAndData[:5], nil, b, headerAndData[:5])
|
||||
c.AEAD.Seal(headerAndData[:5], nil, b, headerAndData[:5])
|
||||
if max {
|
||||
c.GCM = NewGCM(headerAndData, c.UnitedKey)
|
||||
c.AEAD = NewAEAD(headerAndData, c.UnitedKey, c.UseAES)
|
||||
}
|
||||
if c.PreWrite != nil {
|
||||
headerAndData = append(c.PreWrite, headerAndData...)
|
||||
@@ -71,12 +76,12 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
if c.PeerGCM == nil { // client's 0-RTT
|
||||
if c.PeerAEAD == nil { // client's 0-RTT
|
||||
serverRandom := make([]byte, 16)
|
||||
if _, err := io.ReadFull(c.Conn, serverRandom); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.PeerGCM = NewGCM(serverRandom, c.UnitedKey)
|
||||
c.PeerAEAD = NewAEAD(serverRandom, c.UnitedKey, c.UseAES)
|
||||
if xorConn, ok := c.Conn.(*XorConn); ok {
|
||||
xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom)
|
||||
}
|
||||
@@ -85,7 +90,7 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := c.PeerGCM.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
|
||||
if _, err := c.PeerAEAD.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
c.PeerPadding = nil
|
||||
@@ -93,11 +98,11 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if c.input.Len() > 0 {
|
||||
return c.input.Read(b)
|
||||
}
|
||||
peerHeader := make([]byte, 5)
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
|
||||
peerHeader := [5]byte{}
|
||||
if _, err := io.ReadFull(c.Conn, peerHeader[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
l, err := DecodeHeader(peerHeader) // l: 17~17000
|
||||
l, err := DecodeHeader(peerHeader[:]) // l: 17~17000
|
||||
if err != nil {
|
||||
if c.Client != nil && errors.Is(err, ErrInvalidHeader) { // client's 0-RTT
|
||||
c.Client.RWLock.Lock()
|
||||
@@ -110,7 +115,9 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
c.Client = nil
|
||||
c.rawInput.Grow(l)
|
||||
if c.rawInput.Cap() < l {
|
||||
c.rawInput.Grow(l) // no need to use sync.Pool, because we are always reading
|
||||
}
|
||||
peerData := c.rawInput.Bytes()[:l]
|
||||
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
|
||||
return 0, err
|
||||
@@ -119,13 +126,13 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
if len(dst) <= len(b) {
|
||||
dst = b[:len(dst)] // avoids another copy()
|
||||
}
|
||||
var newGCM *GCM
|
||||
if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) {
|
||||
newGCM = NewGCM(append(peerHeader, peerData...), c.UnitedKey)
|
||||
var newAEAD *AEAD
|
||||
if bytes.Equal(c.PeerAEAD.Nonce[:], MaxNonce) {
|
||||
newAEAD = NewAEAD(append(peerHeader[:], peerData...), c.UnitedKey, c.UseAES)
|
||||
}
|
||||
_, err = c.PeerGCM.Open(dst[:0], nil, peerData, peerHeader)
|
||||
if newGCM != nil {
|
||||
c.PeerGCM = newGCM
|
||||
_, err = c.PeerAEAD.Open(dst[:0], nil, peerData, peerHeader[:])
|
||||
if newAEAD != nil {
|
||||
c.PeerAEAD = newAEAD
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -137,28 +144,32 @@ func (c *CommonConn) Read(b []byte) (int, error) {
|
||||
return len(dst), nil
|
||||
}
|
||||
|
||||
type GCM struct {
|
||||
type AEAD struct {
|
||||
cipher.AEAD
|
||||
Nonce [12]byte
|
||||
}
|
||||
|
||||
func NewGCM(ctx, key []byte) *GCM {
|
||||
func NewAEAD(ctx, key []byte, useAES bool) *AEAD {
|
||||
k := make([]byte, 32)
|
||||
blake3.DeriveKey(k, string(ctx), key)
|
||||
block, _ := aes.NewCipher(k)
|
||||
aead, _ := cipher.NewGCM(block)
|
||||
return &GCM{AEAD: aead}
|
||||
//chacha20poly1305.New()
|
||||
var aead cipher.AEAD
|
||||
if useAES {
|
||||
block, _ := aes.NewCipher(k)
|
||||
aead, _ = cipher.NewGCM(block)
|
||||
} else {
|
||||
aead, _ = chacha20poly1305.New(k)
|
||||
}
|
||||
return &AEAD{AEAD: aead}
|
||||
}
|
||||
|
||||
func (a *GCM) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
func (a *AEAD) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
if nonce == nil {
|
||||
nonce = IncreaseNonce(a.Nonce[:])
|
||||
}
|
||||
return a.AEAD.Seal(dst, nonce, plaintext, additionalData)
|
||||
}
|
||||
|
||||
func (a *GCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
func (a *AEAD) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if nonce == nil {
|
||||
nonce = IncreaseNonce(a.Nonce[:])
|
||||
}
|
||||
@@ -206,9 +217,80 @@ func DecodeHeader(h []byte) (l int, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func ParsePadding(padding string, paddingLens, paddingGaps *[][3]int) (err error) {
|
||||
if padding == "" {
|
||||
return
|
||||
}
|
||||
maxLen := 0
|
||||
for i, s := range strings.Split(padding, ".") {
|
||||
x := strings.Split(s, "-")
|
||||
if len(x) < 3 || x[0] == "" || x[1] == "" || x[2] == "" {
|
||||
return errors.New("invalid padding lenth/gap parameter: " + s)
|
||||
}
|
||||
y := [3]int{}
|
||||
if y[0], err = strconv.Atoi(x[0]); err != nil {
|
||||
return
|
||||
}
|
||||
if y[1], err = strconv.Atoi(x[1]); err != nil {
|
||||
return
|
||||
}
|
||||
if y[2], err = strconv.Atoi(x[2]); err != nil {
|
||||
return
|
||||
}
|
||||
if i == 0 && (y[0] < 100 || y[1] < 18+17 || y[2] < 18+17) {
|
||||
return errors.New("first padding length must not be smaller than 35")
|
||||
}
|
||||
if i%2 == 0 {
|
||||
*paddingLens = append(*paddingLens, y)
|
||||
maxLen += max(y[1], y[2])
|
||||
} else {
|
||||
*paddingGaps = append(*paddingGaps, y)
|
||||
}
|
||||
}
|
||||
if maxLen > 18+65535 {
|
||||
return errors.New("total padding length must not be larger than 65553")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func CreatPadding(paddingLens, paddingGaps [][3]int) (length int, lens []int, gaps []time.Duration) {
|
||||
if len(paddingLens) == 0 {
|
||||
paddingLens = [][3]int{{100, 111, 1111}, {50, 0, 3333}}
|
||||
paddingGaps = [][3]int{{75, 0, 111}}
|
||||
}
|
||||
for _, y := range paddingLens {
|
||||
l := 0
|
||||
if y[0] >= int(randBetween(0, 100)) {
|
||||
l = int(randBetween(int64(y[1]), int64(y[2])))
|
||||
}
|
||||
lens = append(lens, l)
|
||||
length += l
|
||||
}
|
||||
for _, y := range paddingGaps {
|
||||
g := 0
|
||||
if y[0] >= int(randBetween(0, 100)) {
|
||||
g = int(randBetween(int64(y[1]), int64(y[2])))
|
||||
}
|
||||
gaps = append(gaps, time.Duration(g)*time.Millisecond)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func max[T ~int | ~uint | ~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~int8 | ~uint8](a, b T) T {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func randBetween(from int64, to int64) int64 {
|
||||
if from == to {
|
||||
return from
|
||||
}
|
||||
|
||||
if to < from {
|
||||
from, to = to, from
|
||||
}
|
||||
|
||||
return from + randv2.Int64N(to-from)
|
||||
}
|
||||
|
@@ -21,4 +21,7 @@
|
||||
// https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902
|
||||
// https://github.com/XTLS/Xray-core/commit/fce1195b60f48ca18a953dbd5c7d991869de9a5e
|
||||
// https://github.com/XTLS/Xray-core/commit/b0b220985c9c1bc832665458d5fd6e0c287b67ae
|
||||
// https://github.com/XTLS/Xray-core/commit/82ea7a3cc5ff23280b87e3052f0f83b04f0267fa
|
||||
// https://github.com/XTLS/Xray-core/commit/e8b02cd6649f14889841e8ab8ee6b2acca71dbe6
|
||||
// https://github.com/XTLS/Xray-core/commit/6768a22f676c9121cfc9dc4f51181a8a07837c8d
|
||||
package encryption
|
||||
|
@@ -34,7 +34,12 @@ func NewClient(encryption string) (*ClientInstance, error) {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
}
|
||||
var nfsPKeysBytes [][]byte
|
||||
var paddings []string
|
||||
for _, r := range s[3:] {
|
||||
if len(r) < 20 {
|
||||
paddings = append(paddings, r)
|
||||
continue
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
|
||||
@@ -44,8 +49,9 @@ func NewClient(encryption string) (*ClientInstance, error) {
|
||||
}
|
||||
nfsPKeysBytes = append(nfsPKeysBytes, b)
|
||||
}
|
||||
padding := strings.Join(paddings, ".")
|
||||
client := &ClientInstance{}
|
||||
if err := client.Init(nfsPKeysBytes, xorMode, seconds); err != nil {
|
||||
if err := client.Init(nfsPKeysBytes, xorMode, seconds, padding); err != nil {
|
||||
return nil, fmt.Errorf("failed to use encryption: %w", err)
|
||||
}
|
||||
return client, nil
|
||||
@@ -84,7 +90,12 @@ func NewServer(decryption string) (*ServerInstance, error) {
|
||||
seconds = uint32(i)
|
||||
}
|
||||
var nfsSKeysBytes [][]byte
|
||||
var paddings []string
|
||||
for _, r := range s[3:] {
|
||||
if len(r) < 20 {
|
||||
paddings = append(paddings, r)
|
||||
continue
|
||||
}
|
||||
b, err := base64.RawURLEncoding.DecodeString(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
|
||||
@@ -94,8 +105,9 @@ func NewServer(decryption string) (*ServerInstance, error) {
|
||||
}
|
||||
nfsSKeysBytes = append(nfsSKeysBytes, b)
|
||||
}
|
||||
padding := strings.Join(paddings, ".")
|
||||
server := &ServerInstance{}
|
||||
if err := server.Init(nfsSKeysBytes, xorMode, seconds); err != nil {
|
||||
if err := server.Init(nfsSKeysBytes, xorMode, seconds, padding); err != nil {
|
||||
return nil, fmt.Errorf("failed to use decryption: %w", err)
|
||||
}
|
||||
return server, nil
|
||||
|
@@ -29,21 +29,21 @@ type ServerInstance struct {
|
||||
RelaysLength int
|
||||
XorMode uint32
|
||||
Seconds uint32
|
||||
PaddingLens [][3]int
|
||||
PaddingGaps [][3]int
|
||||
|
||||
RWLock sync.RWMutex
|
||||
Sessions map[[16]byte]*ServerSession
|
||||
Closed bool
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (err error) {
|
||||
func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
|
||||
if i.NfsSKeys != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
l := len(nfsSKeysBytes)
|
||||
if l == 0 {
|
||||
err = errors.New("empty nfsSKeysBytes")
|
||||
return
|
||||
return errors.New("empty nfsSKeysBytes")
|
||||
}
|
||||
i.NfsSKeys = make([]any, l)
|
||||
i.NfsPKeysBytes = make([][]byte, l)
|
||||
@@ -87,7 +87,7 @@ func (i *ServerInstance) Init(nfsSKeysBytes [][]byte, xorMode, seconds uint32) (
|
||||
}
|
||||
}()
|
||||
}
|
||||
return
|
||||
return ParsePadding(padding, &i.PaddingLens, &i.PaddingGaps)
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Close() (err error) {
|
||||
@@ -97,16 +97,19 @@ func (i *ServerInstance) Close() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
func (i *ServerInstance) Handshake(conn net.Conn, fallback *[]byte) (*CommonConn, error) {
|
||||
if i.NfsSKeys == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
c := NewCommonConn(conn)
|
||||
c := NewCommonConn(conn, true)
|
||||
|
||||
ivAndRelays := make([]byte, 16+i.RelaysLength)
|
||||
if _, err := io.ReadFull(conn, ivAndRelays); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fallback != nil {
|
||||
*fallback = append(*fallback, ivAndRelays...)
|
||||
}
|
||||
iv := ivAndRelays[:16]
|
||||
relays := ivAndRelays[16:]
|
||||
var nfsKey []byte
|
||||
@@ -150,16 +153,27 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
}
|
||||
relays = relays[32:]
|
||||
}
|
||||
nfsGCM := NewGCM(iv, nfsKey)
|
||||
nfsAEAD := NewAEAD(iv, nfsKey, c.UseAES)
|
||||
|
||||
encryptedLength := make([]byte, 18)
|
||||
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
if fallback != nil {
|
||||
*fallback = append(*fallback, encryptedLength...)
|
||||
}
|
||||
length := DecodeLength(encryptedLength[:2])
|
||||
decryptedLength := make([]byte, 2)
|
||||
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
c.UseAES = !c.UseAES
|
||||
nfsAEAD = NewAEAD(iv, nfsKey, c.UseAES)
|
||||
if _, err := nfsAEAD.Open(decryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if fallback != nil {
|
||||
*fallback = nil
|
||||
}
|
||||
length := DecodeLength(decryptedLength)
|
||||
|
||||
if length == 32 {
|
||||
if i.Seconds == 0 {
|
||||
@@ -169,7 +183,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if _, err := io.ReadFull(conn, encryptedTicket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ticket, err := nfsGCM.Open(nil, nil, encryptedTicket, nil)
|
||||
ticket, err := nfsAEAD.Open(nil, nil, encryptedTicket, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,7 +191,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
s := i.Sessions[[16]byte(ticket)]
|
||||
i.RWLock.RUnlock()
|
||||
if s == nil {
|
||||
noises := make([]byte, randBetween(1268, 2268)) // matches 1-RTT's server hello length for "random", though it is not important, just for example
|
||||
noises := make([]byte, randBetween(1279, 2279)) // matches 1-RTT's server hello length for "random", though it is not important, just for example
|
||||
var err error
|
||||
for err == nil {
|
||||
rand.Read(noises)
|
||||
@@ -192,8 +206,8 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download (prevents server -> client's another request)
|
||||
c.PreWrite = make([]byte, 16)
|
||||
rand.Read(c.PreWrite) // always trust yourself, not the client (also prevents being parsed as TLS thus causing false interruption for "native" and "xorpub")
|
||||
c.GCM = NewGCM(c.PreWrite, c.UnitedKey)
|
||||
c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client)
|
||||
c.AEAD = NewAEAD(c.PreWrite, c.UnitedKey, c.UseAES)
|
||||
c.PeerAEAD = NewAEAD(encryptedTicket, c.UnitedKey, c.UseAES) // unchangeable ctx (prevents server -> server), and different ctx length for upload / download (prevents client -> client)
|
||||
if i.XorMode == 2 {
|
||||
c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client
|
||||
}
|
||||
@@ -207,7 +221,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil {
|
||||
if _, err := nfsAEAD.Open(encryptedPfsPublicKey[:0], nil, encryptedPfsPublicKey, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mlkem768EKey, err := mlkem.NewEncapsulationKey768(encryptedPfsPublicKey[:1184])
|
||||
@@ -229,27 +243,12 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
copy(pfsKey[32:], x25519Key)
|
||||
pfsPublicKey := append(encapsulatedPfsKey, x25519SKey.PublicKey().Bytes()...)
|
||||
c.UnitedKey = append(pfsKey, nfsKey...)
|
||||
c.GCM = NewGCM(pfsPublicKey, c.UnitedKey)
|
||||
c.PeerGCM = NewGCM(encryptedPfsPublicKey[:1184+32], c.UnitedKey)
|
||||
c.AEAD = NewAEAD(pfsPublicKey, c.UnitedKey, c.UseAES)
|
||||
c.PeerAEAD = NewAEAD(encryptedPfsPublicKey[:1184+32], c.UnitedKey, c.UseAES)
|
||||
|
||||
ticket := make([]byte, 16)
|
||||
rand.Read(ticket)
|
||||
copy(ticket, EncodeLength(int(i.Seconds*4/5)))
|
||||
|
||||
pfsKeyExchangeLength := 1088 + 32 + 16
|
||||
encryptedTicketLength := 32
|
||||
paddingLength := int(randBetween(100, 1000))
|
||||
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
|
||||
nfsGCM.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
|
||||
c.GCM.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
|
||||
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
|
||||
c.GCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
c.GCM.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
|
||||
if _, err := conn.Write(serverHello); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
|
||||
if i.Seconds > 0 {
|
||||
i.RWLock.Lock()
|
||||
i.Sessions[[16]byte(ticket)] = &ServerSession{
|
||||
@@ -259,18 +258,41 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) {
|
||||
i.RWLock.Unlock()
|
||||
}
|
||||
|
||||
pfsKeyExchangeLength := 1088 + 32 + 16
|
||||
encryptedTicketLength := 32
|
||||
paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
|
||||
serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength)
|
||||
nfsAEAD.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil)
|
||||
c.AEAD.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil)
|
||||
padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:]
|
||||
c.AEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
|
||||
c.AEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
|
||||
|
||||
paddingLens[0] = pfsKeyExchangeLength + encryptedTicketLength + paddingLens[0]
|
||||
for i, l := range paddingLens { // sends padding in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control
|
||||
if l > 0 {
|
||||
if _, err := conn.Write(serverHello[:l]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverHello = serverHello[l:]
|
||||
}
|
||||
if len(paddingGaps) > i {
|
||||
time.Sleep(paddingGaps[i])
|
||||
}
|
||||
}
|
||||
|
||||
// important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern
|
||||
if _, err := io.ReadFull(conn, encryptedLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
if _, err := nfsAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2]))
|
||||
if _, err := io.ReadFull(conn, encryptedPadding); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := nfsGCM.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil {
|
||||
if _, err := nfsAEAD.Open(encryptedPadding[:0], nil, encryptedPadding, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user