VLESS protocol: Add lightweight Post-Quantum ML-KEM-768-based PFS 1-RTT / anti-replay 0-RTT AEAD encryption

https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3163335040
This commit is contained in:
RPRX
2025-08-10 11:50:18 +00:00
committed by GitHub
parent 0cceea75da
commit f61c14e9c6
18 changed files with 769 additions and 68 deletions

View File

@@ -18,6 +18,7 @@ func (a *Account) AsAccount() (protocol.Account, error) {
ID: protocol.NewID(id),
Flow: a.Flow, // needs parser here?
Encryption: a.Encryption, // needs parser here?
Minutes: a.Minutes,
}, nil
}
@@ -27,8 +28,9 @@ type MemoryAccount struct {
ID *protocol.ID
// Flow of the account. May be "xtls-rprx-vision".
Flow string
// Encryption of the account. Used for client connections, and only accepts "none" for now.
Encryption string
Minutes uint32
}
// Equals implements protocol.Account.Equals().
@@ -45,5 +47,6 @@ func (a *MemoryAccount) ToProto() proto.Message {
Id: a.ID.String(),
Flow: a.Flow,
Encryption: a.Encryption,
Minutes: a.Minutes,
}
}

View File

@@ -28,9 +28,9 @@ type Account struct {
// ID of the account, in the form of a UUID, e.g., "66ad4540-b58c-4ad2-9926-ea63445a9b57".
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
// Flow settings. May be "xtls-rprx-vision".
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
// Encryption settings. Only applies to client side, and only accepts "none" for now.
Flow string `protobuf:"bytes,2,opt,name=flow,proto3" json:"flow,omitempty"`
Encryption string `protobuf:"bytes,3,opt,name=encryption,proto3" json:"encryption,omitempty"`
Minutes uint32 `protobuf:"varint,4,opt,name=minutes,proto3" json:"minutes,omitempty"`
}
func (x *Account) Reset() {
@@ -84,23 +84,32 @@ func (x *Account) GetEncryption() string {
return ""
}
func (x *Account) GetMinutes() uint32 {
if x != nil {
return x.Minutes
}
return 0
}
var File_proxy_vless_account_proto protoreflect.FileDescriptor
var file_proxy_vless_account_proto_rawDesc = []byte{
0x0a, 0x19, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x63,
0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x78, 0x72, 0x61,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x4d, 0x0a,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x22, 0x67, 0x0a,
0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x6c, 0x6f, 0x77,
0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1e, 0x0a, 0x0a,
0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x52, 0x0a, 0x14,
0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x6c, 0x65, 0x73, 0x73, 0x50, 0x01, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10,
0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x52, 0x0a, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d,
0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x42, 0x52, 0x0a, 0x14, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x50, 0x01,
0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c,
0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78,
0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0xaa, 0x02, 0x10, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50,
0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (

View File

@@ -11,6 +11,7 @@ message Account {
string id = 1;
// Flow settings. May be "xtls-rprx-vision".
string flow = 2;
// Encryption settings. Only applies to client side, and only accepts "none" for now.
string encryption = 3;
uint32 minutes = 4;
}

View File

@@ -0,0 +1,228 @@
package encryption
import (
"bytes"
"crypto/cipher"
"crypto/mlkem"
"crypto/rand"
"crypto/sha256"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/protocol"
"golang.org/x/crypto/hkdf"
)
var ClientCipher byte
func init() {
if !protocol.HasAESGCMHardwareSupport {
ClientCipher = 1
}
}
type ClientInstance struct {
sync.RWMutex
eKeyNfs *mlkem.EncapsulationKey768
minutes time.Duration
expire time.Time
baseKey []byte
reuse []byte
}
type ClientConn struct {
net.Conn
instance *ClientInstance
baseKey []byte
reuse []byte
random []byte
aead cipher.AEAD
nonce []byte
peerAead cipher.AEAD
peerNonce []byte
peerCache []byte
}
func (i *ClientInstance) Init(eKeyNfsData []byte, minutes time.Duration) (err error) {
i.eKeyNfs, err = mlkem.NewEncapsulationKey768(eKeyNfsData)
i.minutes = minutes
return
}
func (i *ClientInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.eKeyNfs == nil {
return nil, errors.New("uninitialized")
}
c := &ClientConn{Conn: conn}
if i.minutes > 0 {
i.RLock()
if time.Now().Before(i.expire) {
c.instance = i
c.baseKey = i.baseKey
c.reuse = i.reuse
i.RUnlock()
return c, nil
}
i.RUnlock()
}
nfsKey, encapsulatedNfsKey := i.eKeyNfs.Encapsulate()
seed := make([]byte, 64)
rand.Read(seed)
dKeyPfs, _ := mlkem.NewDecapsulationKey768(seed)
eKeyPfs := dKeyPfs.EncapsulationKey().Bytes()
padding := crypto.RandBetween(100, 1000)
clientHello := make([]byte, 1088+1184+1+5+padding)
copy(clientHello, encapsulatedNfsKey)
copy(clientHello[1088:], eKeyPfs)
clientHello[2272] = ClientCipher
encodeHeader(clientHello[2273:], int(padding))
if _, err := c.Conn.Write(clientHello); err != nil {
return nil, err
}
// we can send more padding if needed
peerServerHello := make([]byte, 1088+21)
if _, err := io.ReadFull(c.Conn, peerServerHello); err != nil {
return nil, err
}
encapsulatedPfsKey := peerServerHello[:1088]
c.reuse = peerServerHello[1088:]
pfsKey, err := dKeyPfs.Decapsulate(encapsulatedPfsKey)
if err != nil {
return nil, err
}
c.baseKey = append(nfsKey, pfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfs).Read(authKey)
nonce := make([]byte, 12)
VLESS, _ := newAead(ClientCipher, authKey).Open(nil, nonce, c.reuse, encapsulatedPfsKey)
if !bytes.Equal(VLESS, []byte("VLESS")) { // TODO: more message
return nil, errors.New("invalid server").AtError()
}
if i.minutes > 0 {
i.Lock()
i.expire = time.Now().Add(i.minutes)
i.baseKey = c.baseKey
i.reuse = c.reuse
i.Unlock()
}
return c, nil
}
func (c *ClientConn) Write(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
var data []byte
if c.aead == nil {
c.random = make([]byte, 32)
rand.Read(c.random)
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.random, c.reuse).Read(key)
c.aead = newAead(ClientCipher, key)
c.nonce = make([]byte, 12)
data = make([]byte, 21+32+5+len(b)+16)
copy(data, c.reuse)
copy(data[21:], c.random)
encodeHeader(data[53:], len(b)+16)
c.aead.Seal(data[:58], c.nonce, b, data[53:58])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
return len(b), nil
}
func (c *ClientConn) Read(b []byte) (int, error) { // after first Write()
if len(b) == 0 {
return 0, nil
}
peerHeader := make([]byte, 5)
if c.peerAead == nil {
if c.instance == nil {
for {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
return 0, err
}
}
} else {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
}
peerRandom := make([]byte, 32)
copy(peerRandom, peerHeader)
if _, err := io.ReadFull(c.Conn, peerRandom[5:]); err != nil {
return 0, err
}
if c.random == nil {
return 0, errors.New("can not Read() first")
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, peerRandom, c.random).Read(peerKey)
c.peerAead = newAead(ClientCipher, peerKey)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
n := copy(b, c.peerCache)
c.peerCache = c.peerCache[n:]
return n, nil
}
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
if err != nil {
if c.instance != nil {
c.instance.Lock()
if bytes.Equal(c.reuse, c.instance.reuse) {
c.instance.expire = time.Now() // expired
}
c.instance.Unlock()
}
return 0, err
}
peerData := make([]byte, peerLength)
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
return 0, err
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if err != nil {
return 0, err
}
if len(dst) > len(b) {
c.peerCache = dst[copy(b, dst):]
dst = b // for len(dst)
}
return len(dst), nil
}

View File

@@ -0,0 +1,55 @@
package encryption
import (
"crypto/aes"
"crypto/cipher"
"strconv"
"github.com/xtls/xray-core/common/errors"
"golang.org/x/crypto/chacha20poly1305"
)
func encodeHeader(b []byte, l int) {
b[0] = 23
b[1] = 3
b[2] = 3
b[3] = byte(l >> 8)
b[4] = byte(l)
}
func decodeHeader(b []byte) (int, error) {
if b[0] == 23 && b[1] == 3 && b[2] == 3 {
l := int(b[3])<<8 | int(b[4])
if l < 17 || l > 17000 { // TODO
return 0, errors.New("invalid length in record's header: " + strconv.Itoa(l))
}
return l, nil
}
return 0, errors.New("invalid record's header")
}
func newAead(c byte, k []byte) cipher.AEAD {
switch c {
case 0:
if block, err := aes.NewCipher(k); err == nil {
aead, _ := cipher.NewGCM(block)
return aead
}
case 1:
aead, _ := chacha20poly1305.New(k)
return aead
}
return nil
}
func increaseNonce(nonce []byte) {
for i := range 12 {
nonce[11-i]++
if nonce[11-i] != 0 {
break
}
if i == 11 {
// TODO
}
}
}

View File

@@ -0,0 +1,251 @@
package encryption
import (
"bytes"
"crypto/cipher"
"crypto/mlkem"
"crypto/rand"
"crypto/sha256"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"golang.org/x/crypto/hkdf"
)
type ServerSession struct {
expire time.Time
cipher byte
baseKey []byte
randoms sync.Map
}
type ServerInstance struct {
sync.RWMutex
dKeyNfs *mlkem.DecapsulationKey768
minutes time.Duration
sessions map[[21]byte]*ServerSession
}
type ServerConn struct {
net.Conn
cipher byte
baseKey []byte
reuse []byte
peerRandom []byte
peerAead cipher.AEAD
peerNonce []byte
peerCache []byte
aead cipher.AEAD
nonce []byte
}
func (i *ServerInstance) Init(dKeyNfsData []byte, minutes time.Duration) (err error) {
i.dKeyNfs, err = mlkem.NewDecapsulationKey768(dKeyNfsData)
if minutes > 0 {
i.minutes = minutes
i.sessions = make(map[[21]byte]*ServerSession)
go func() {
for {
time.Sleep(time.Minute)
now := time.Now()
i.Lock()
for index, session := range i.sessions {
if now.After(session.expire) {
delete(i.sessions, index)
}
}
i.Unlock()
}
}()
}
return
}
func (i *ServerInstance) Handshake(conn net.Conn) (net.Conn, error) {
if i.dKeyNfs == nil {
return nil, errors.New("uninitialized")
}
c := &ServerConn{Conn: conn}
peerReuseHello := make([]byte, 21+32)
if _, err := io.ReadFull(c.Conn, peerReuseHello); err != nil {
return nil, err
}
if i.minutes > 0 {
i.RLock()
s := i.sessions[[21]byte(peerReuseHello)]
i.RUnlock()
if s != nil {
if _, replay := s.randoms.LoadOrStore([32]byte(peerReuseHello[21:]), true); !replay {
c.cipher = s.cipher
c.baseKey = s.baseKey
c.reuse = peerReuseHello[:21]
c.peerRandom = peerReuseHello[21:]
return c, nil
}
}
}
peerHeader := make([]byte, 5)
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return nil, err
}
if l, _ := decodeHeader(peerHeader); l != 0 {
c.Conn.Write(make([]byte, crypto.RandBetween(100, 1000))) // make client do new handshake
return nil, errors.New("invalid reuse")
}
peerClientHello := make([]byte, 1088+1184+1)
copy(peerClientHello, peerReuseHello)
copy(peerClientHello[53:], peerHeader)
if _, err := io.ReadFull(c.Conn, peerClientHello[58:]); err != nil {
return nil, err
}
encapsulatedNfsKey := peerClientHello[:1088]
eKeyPfsData := peerClientHello[1088:2272]
c.cipher = peerClientHello[2272]
if c.cipher != 0 && c.cipher != 1 {
return nil, errors.New("invalid cipher")
}
nfsKey, err := i.dKeyNfs.Decapsulate(encapsulatedNfsKey)
if err != nil {
return nil, err
}
eKeyPfs, err := mlkem.NewEncapsulationKey768(eKeyPfsData)
if err != nil {
return nil, err
}
pfsKey, encapsulatedPfsKey := eKeyPfs.Encapsulate()
c.baseKey = append(nfsKey, pfsKey...)
authKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, encapsulatedNfsKey, eKeyPfsData).Read(authKey)
nonce := make([]byte, 12)
c.reuse = newAead(c.cipher, authKey).Seal(nil, nonce, []byte("VLESS"), encapsulatedPfsKey)
padding := crypto.RandBetween(100, 1000)
serverHello := make([]byte, 1088+21+5+padding)
copy(serverHello, encapsulatedPfsKey)
copy(serverHello[1088:], c.reuse)
encodeHeader(serverHello[1109:], int(padding))
if _, err := c.Conn.Write(serverHello); err != nil {
return nil, err
}
if i.minutes > 0 {
i.Lock()
i.sessions[[21]byte(c.reuse)] = &ServerSession{
expire: time.Now().Add(i.minutes),
cipher: c.cipher,
baseKey: c.baseKey,
}
i.Unlock()
}
return c, nil
}
func (c *ServerConn) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
peerHeader := make([]byte, 5)
if c.peerAead == nil {
if c.peerRandom == nil {
for {
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerPadding, _ := decodeHeader(peerHeader)
if peerPadding == 0 {
break
}
if _, err := io.ReadFull(c.Conn, make([]byte, peerPadding)); err != nil {
return 0, err
}
}
peerIndex := make([]byte, 21)
copy(peerIndex, peerHeader)
if _, err := io.ReadFull(c.Conn, peerIndex[5:]); err != nil {
return 0, err
}
if !bytes.Equal(peerIndex, c.reuse) {
return 0, errors.New("naughty boy")
}
c.peerRandom = make([]byte, 32)
if _, err := io.ReadFull(c.Conn, c.peerRandom); err != nil {
return 0, err
}
}
peerKey := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, c.peerRandom, c.reuse).Read(peerKey)
c.peerAead = newAead(c.cipher, peerKey)
c.peerNonce = make([]byte, 12)
}
if len(c.peerCache) != 0 {
n := copy(b, c.peerCache)
c.peerCache = c.peerCache[n:]
return n, nil
}
if _, err := io.ReadFull(c.Conn, peerHeader); err != nil {
return 0, err
}
peerLength, err := decodeHeader(peerHeader) // 17~17000
if err != nil {
return 0, err
}
peerData := make([]byte, peerLength)
if _, err := io.ReadFull(c.Conn, peerData); err != nil {
return 0, err
}
dst := peerData[:peerLength-16]
if len(dst) <= len(b) {
dst = b[:len(dst)] // max=8192 is recommended for peer
}
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, peerHeader)
increaseNonce(c.peerNonce)
if err != nil {
return 0, errors.New("error")
}
if len(dst) > len(b) {
c.peerCache = dst[copy(b, dst):]
dst = b // for len(dst)
}
return len(dst), nil
}
func (c *ServerConn) Write(b []byte) (int, error) { // after first Read()
if len(b) == 0 {
return 0, nil
}
var data []byte
if c.aead == nil {
if c.peerRandom == nil {
return 0, errors.New("can not Write() first")
}
data = make([]byte, 32+5+len(b)+16)
rand.Read(data[:32])
key := make([]byte, 32)
hkdf.New(sha256.New, c.baseKey, data[:32], c.peerRandom).Read(key)
c.aead = newAead(c.cipher, key)
c.nonce = make([]byte, 12)
encodeHeader(data[32:], len(b)+16)
c.aead.Seal(data[:37], c.nonce, b, data[32:37])
} else {
data = make([]byte, 5+len(b)+16)
encodeHeader(data, len(b)+16)
c.aead.Seal(data[:5], c.nonce, b, data[:5])
}
increaseNonce(c.nonce)
if _, err := c.Conn.Write(data); err != nil {
return 0, err
}
return len(b), nil
}

View File

@@ -111,11 +111,10 @@ type Config struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
// Decryption settings. Only applies to server side, and only accepts "none"
// for now.
Decryption string `protobuf:"bytes,2,opt,name=decryption,proto3" json:"decryption,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,3,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
Clients []*protocol.User `protobuf:"bytes,1,rep,name=clients,proto3" json:"clients,omitempty"`
Fallbacks []*Fallback `protobuf:"bytes,2,rep,name=fallbacks,proto3" json:"fallbacks,omitempty"`
Decryption string `protobuf:"bytes,3,opt,name=decryption,proto3" json:"decryption,omitempty"`
Minutes uint32 `protobuf:"varint,4,opt,name=minutes,proto3" json:"minutes,omitempty"`
}
func (x *Config) Reset() {
@@ -155,6 +154,13 @@ func (x *Config) GetClients() []*protocol.User {
return nil
}
func (x *Config) GetFallbacks() []*Fallback {
if x != nil {
return x.Fallbacks
}
return nil
}
func (x *Config) GetDecryption() string {
if x != nil {
return x.Decryption
@@ -162,11 +168,11 @@ func (x *Config) GetDecryption() string {
return ""
}
func (x *Config) GetFallbacks() []*Fallback {
func (x *Config) GetMinutes() uint32 {
if x != nil {
return x.Fallbacks
return x.Minutes
}
return nil
return 0
}
var File_proxy_vless_inbound_config_proto protoreflect.FileDescriptor
@@ -185,25 +191,26 @@ var file_proxy_vless_inbound_config_proto_rawDesc = []byte{
0x68, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x74, 0x18, 0x05, 0x20,
0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x78, 0x76, 0x65,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xa0, 0x01,
0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x78, 0x76, 0x65, 0x72, 0x22, 0xba, 0x01,
0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65,
0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72, 0x61, 0x79,
0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x1e,
0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x40,
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x40,
0x0a, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76,
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x2e, 0x46, 0x61, 0x6c,
0x6c, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x09, 0x66, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x73,
0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f,
0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72,
0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c, 0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e,
0x64, 0xaa, 0x02, 0x18, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56,
0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e,
0x12, 0x18, 0x0a, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0d, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x73, 0x42, 0x6a, 0x0a, 0x1c, 0x63, 0x6f,
0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x76, 0x6c, 0x65,
0x73, 0x73, 0x2e, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x01, 0x5a, 0x2d, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72,
0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x76, 0x6c,
0x65, 0x73, 0x73, 0x2f, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0xaa, 0x02, 0x18, 0x58, 0x72,
0x61, 0x79, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x2e, 0x56, 0x6c, 0x65, 0x73, 0x73, 0x2e, 0x49,
0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (

View File

@@ -19,8 +19,8 @@ message Fallback {
message Config {
repeated xray.common.protocol.User clients = 1;
// Decryption settings. Only applies to server side, and only accepts "none"
// for now.
string decryption = 2;
repeated Fallback fallbacks = 3;
repeated Fallback fallbacks = 2;
string decryption = 3;
uint32 minutes = 4;
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
gotls "crypto/tls"
"encoding/base64"
"io"
"reflect"
"strconv"
@@ -29,6 +30,7 @@ import (
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/encoding"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -67,6 +69,7 @@ type Handler struct {
policyManager policy.Manager
validator vless.Validator
dns dns.Client
decryption *encryption.ServerInstance
fallbacks map[string]map[string]map[string]*Fallback // or nil
// regexps map[string]*regexp.Regexp // or nil
}
@@ -81,6 +84,14 @@ func New(ctx context.Context, config *Config, dc dns.Client, validator vless.Val
validator: validator,
}
d, _ := base64.RawURLEncoding.DecodeString(config.Decryption)
if len(d) == 64 {
handler.decryption = &encryption.ServerInstance{}
if err := handler.decryption.Init(d, time.Duration(config.Minutes)*time.Minute); err != nil {
return nil, errors.New("failed to use mlkem768seed").Base(err).AtError()
}
}
if config.Fallbacks != nil {
handler.fallbacks = make(map[string]map[string]map[string]*Fallback)
// handler.regexps = make(map[string]*regexp.Regexp)
@@ -204,6 +215,14 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s
return errors.New("unable to set read deadline").Base(err).AtWarning()
}
if h.decryption != nil {
var err error
connection, err = h.decryption.Handshake(connection)
if err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
}
}
first := buf.FromBytes(make([]byte, buf.Size))
first.Clear()
firstLen, errR := first.ReadFrom(connection)

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"context"
gotls "crypto/tls"
"encoding/base64"
"reflect"
"time"
"unsafe"
@@ -24,6 +25,7 @@ import (
"github.com/xtls/xray-core/proxy"
"github.com/xtls/xray-core/proxy/vless"
"github.com/xtls/xray-core/proxy/vless/encoding"
"github.com/xtls/xray-core/proxy/vless/encryption"
"github.com/xtls/xray-core/transport"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/reality"
@@ -43,6 +45,7 @@ type Handler struct {
serverPicker protocol.ServerPicker
policyManager policy.Manager
cone bool
encryption *encryption.ClientInstance
}
// New creates a new VLess outbound handler.
@@ -64,6 +67,15 @@ func New(ctx context.Context, config *Config) (*Handler, error) {
cone: ctx.Value("cone").(bool),
}
a := handler.serverPicker.PickServer().PickUser().Account.(*vless.MemoryAccount)
e, _ := base64.RawURLEncoding.DecodeString(a.Encryption)
if len(e) == 1184 {
handler.encryption = &encryption.ClientInstance{}
if err := handler.encryption.Init(e, time.Duration(a.Minutes)*time.Minute); err != nil {
return nil, errors.New("failed to use mlkem768client").Base(err).AtError()
}
}
return handler, nil
}
@@ -98,6 +110,14 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
target := ob.Target
errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr())
if h.encryption != nil {
var err error
conn, err = h.encryption.Handshake(conn)
if err != nil {
return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo()
}
}
command := protocol.RequestCommandTCP
if target.Network == net.Network_UDP {
command = protocol.RequestCommandUDP