mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-22 17:46:48 +08:00
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:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
@@ -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 (
|
||||
|
@@ -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;
|
||||
}
|
||||
|
228
proxy/vless/encryption/client.go
Normal file
228
proxy/vless/encryption/client.go
Normal 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
|
||||
}
|
55
proxy/vless/encryption/common.go
Normal file
55
proxy/vless/encryption/common.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
251
proxy/vless/encryption/server.go
Normal file
251
proxy/vless/encryption/server.go
Normal 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
|
||||
}
|
@@ -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 (
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user