mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-24 10:36:49 +08:00
Use X25519 for XOR; Add "divide" (ECH, before and includes type 0); Change config format
https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3207449672
This commit is contained in:
@@ -3,6 +3,7 @@ package encryption
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"crypto/sha3"
|
||||
@@ -29,7 +30,8 @@ type ClientInstance struct {
|
||||
sync.RWMutex
|
||||
nfsEKey *mlkem.EncapsulationKey768
|
||||
hash11 [11]byte // no more capacity
|
||||
xorKey []byte
|
||||
xorMode uint32
|
||||
xorPKey *ecdh.PublicKey
|
||||
minutes time.Duration
|
||||
expire time.Time
|
||||
baseKey []byte
|
||||
@@ -49,22 +51,23 @@ type ClientConn struct {
|
||||
PeerCache []byte
|
||||
}
|
||||
|
||||
func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) {
|
||||
func (i *ClientInstance) Init(nfsEKeyBytes, xorPKeyBytes []byte, xorMode, minutes uint32) (err error) {
|
||||
if i.nfsEKey != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
}
|
||||
i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes)
|
||||
if err != nil {
|
||||
if i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes); err != nil {
|
||||
return
|
||||
}
|
||||
hash32 := sha3.Sum256(nfsEKeyBytes)
|
||||
copy(i.hash11[:], hash32[:])
|
||||
if xor > 0 {
|
||||
xorKey := sha3.Sum256(nfsEKeyBytes)
|
||||
i.xorKey = xorKey[:]
|
||||
if xorMode > 0 {
|
||||
i.xorMode = xorMode
|
||||
if i.xorPKey, err = ecdh.X25519().NewPublicKey(xorPKeyBytes); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
i.minutes = minutes
|
||||
i.minutes = time.Duration(minutes) * time.Minute
|
||||
return
|
||||
}
|
||||
|
||||
@@ -72,8 +75,8 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) {
|
||||
if i.nfsEKey == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
if i.xorKey != nil {
|
||||
conn = NewXorConn(conn, i.xorKey)
|
||||
if i.xorMode > 0 {
|
||||
conn, _ = NewXorConn(conn, i.xorMode, i.xorPKey, nil)
|
||||
}
|
||||
c := &ClientConn{Conn: conn}
|
||||
|
||||
@@ -134,7 +137,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) {
|
||||
}
|
||||
c.baseKey = append(pfsKey, nfsKey...)
|
||||
|
||||
VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes)
|
||||
VLESS, _ := NewAEAD(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes)
|
||||
if !bytes.Equal(VLESS, []byte("VLESS")) {
|
||||
return nil, errors.New("invalid server").AtError()
|
||||
}
|
||||
@@ -169,7 +172,7 @@ func (c *ClientConn) Write(b []byte) (int, error) {
|
||||
rand.Read(c.random)
|
||||
copy(data[5+32:], c.random)
|
||||
EncodeHeader(data[5+32+32:], 23, len(b)+16)
|
||||
c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket)
|
||||
c.aead = NewAEAD(ClientCipher, c.baseKey, c.random, c.ticket)
|
||||
c.nonce = make([]byte, 12)
|
||||
c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5])
|
||||
} else {
|
||||
@@ -177,7 +180,7 @@ func (c *ClientConn) Write(b []byte) (int, error) {
|
||||
EncodeHeader(data, 23, len(b)+16)
|
||||
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||
if bytes.Equal(c.nonce, MaxNonce) {
|
||||
c.aead = NewAead(ClientCipher, c.baseKey, data[5:], data[:5])
|
||||
c.aead = NewAEAD(ClientCipher, c.baseKey, data[5:], data[:5])
|
||||
}
|
||||
}
|
||||
IncreaseNonce(c.nonce)
|
||||
@@ -218,7 +221,7 @@ func (c *ClientConn) Read(b []byte) (int, error) {
|
||||
if c.random == nil {
|
||||
return 0, errors.New("empty c.random")
|
||||
}
|
||||
c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandomHello, c.random)
|
||||
c.peerAead = NewAEAD(ClientCipher, c.baseKey, peerRandomHello, c.random)
|
||||
c.peerNonce = make([]byte, 12)
|
||||
}
|
||||
if len(c.PeerCache) != 0 {
|
||||
@@ -243,7 +246,7 @@ func (c *ClientConn) Read(b []byte) (int, error) {
|
||||
}
|
||||
var peerAead cipher.AEAD
|
||||
if bytes.Equal(c.peerNonce, MaxNonce) {
|
||||
peerAead = NewAead(ClientCipher, c.baseKey, peerData, h)
|
||||
peerAead = NewAEAD(ClientCipher, c.baseKey, peerData, h)
|
||||
}
|
||||
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, h)
|
||||
if peerAead != nil {
|
||||
|
@@ -72,7 +72,7 @@ func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error)
|
||||
}
|
||||
}
|
||||
|
||||
func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
|
||||
func NewAEAD(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
|
||||
key, _ := hkdf.Key(sha3.New256, secret, salt, string(info), 32)
|
||||
if c&1 == 1 {
|
||||
block, _ := aes.NewCipher(key)
|
||||
|
@@ -3,6 +3,7 @@ package encryption
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/mlkem"
|
||||
"crypto/rand"
|
||||
"crypto/sha3"
|
||||
@@ -27,7 +28,8 @@ type ServerInstance struct {
|
||||
sync.RWMutex
|
||||
nfsDKey *mlkem.DecapsulationKey768
|
||||
hash11 [11]byte // no more capacity
|
||||
xorKey []byte
|
||||
xorMode uint32
|
||||
xorSKey *ecdh.PrivateKey
|
||||
minutes time.Duration
|
||||
sessions map[[32]byte]*ServerSession
|
||||
closed bool
|
||||
@@ -46,23 +48,24 @@ type ServerConn struct {
|
||||
nonce []byte
|
||||
}
|
||||
|
||||
func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Duration) (err error) {
|
||||
func (i *ServerInstance) Init(nfsDKeySeed, xorSKeyBytes []byte, xorMode, minutes uint32) (err error) {
|
||||
if i.nfsDKey != nil {
|
||||
err = errors.New("already initialized")
|
||||
return
|
||||
}
|
||||
i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed)
|
||||
if err != nil {
|
||||
if i.nfsDKey, err = mlkem.NewDecapsulationKey768(nfsDKeySeed); err != nil {
|
||||
return
|
||||
}
|
||||
hash32 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes())
|
||||
copy(i.hash11[:], hash32[:])
|
||||
if xor > 0 {
|
||||
xorKey := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes())
|
||||
i.xorKey = xorKey[:]
|
||||
if xorMode > 0 {
|
||||
i.xorMode = xorMode
|
||||
if i.xorSKey, err = ecdh.X25519().NewPrivateKey(xorSKeyBytes); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if minutes > 0 {
|
||||
i.minutes = minutes
|
||||
i.minutes = time.Duration(minutes) * time.Minute
|
||||
i.sessions = make(map[[32]byte]*ServerSession)
|
||||
go func() {
|
||||
for {
|
||||
@@ -96,8 +99,11 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) {
|
||||
if i.nfsDKey == nil {
|
||||
return nil, errors.New("uninitialized")
|
||||
}
|
||||
if i.xorKey != nil {
|
||||
conn = NewXorConn(conn, i.xorKey)
|
||||
if i.xorMode > 0 {
|
||||
var err error
|
||||
if conn, err = NewXorConn(conn, i.xorMode, nil, i.xorSKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
c := &ServerConn{Conn: conn}
|
||||
|
||||
@@ -168,7 +174,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*ServerConn, error) {
|
||||
pfsKey, encapsulatedPfsKey := pfsEKey.Encapsulate()
|
||||
c.baseKey = append(pfsKey, nfsKey...)
|
||||
|
||||
c.ticket = append(i.hash11[:], NewAead(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...)
|
||||
c.ticket = append(i.hash11[:], NewAEAD(c.cipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Seal(nil, peerClientHello[:12], []byte("VLESS"), pfsEKeyBytes)...)
|
||||
|
||||
paddingLen := crypto.RandBetween(100, 1000)
|
||||
|
||||
@@ -222,7 +228,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
}
|
||||
c.peerRandom = peerTicketHello[32:]
|
||||
}
|
||||
c.peerAead = NewAead(c.cipher, c.baseKey, c.peerRandom, c.ticket)
|
||||
c.peerAead = NewAEAD(c.cipher, c.baseKey, c.peerRandom, c.ticket)
|
||||
c.peerNonce = make([]byte, 12)
|
||||
}
|
||||
if len(c.PeerCache) != 0 {
|
||||
@@ -247,7 +253,7 @@ func (c *ServerConn) Read(b []byte) (int, error) {
|
||||
}
|
||||
var peerAead cipher.AEAD
|
||||
if bytes.Equal(c.peerNonce, MaxNonce) {
|
||||
peerAead = NewAead(c.cipher, c.baseKey, peerData, h)
|
||||
peerAead = NewAEAD(c.cipher, c.baseKey, peerData, h)
|
||||
}
|
||||
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, h)
|
||||
if peerAead != nil {
|
||||
@@ -283,7 +289,7 @@ func (c *ServerConn) Write(b []byte) (int, error) {
|
||||
EncodeHeader(data, 0, 32)
|
||||
rand.Read(data[5 : 5+32])
|
||||
EncodeHeader(data[5+32:], 23, len(b)+16)
|
||||
c.aead = NewAead(c.cipher, c.baseKey, data[5:5+32], c.peerRandom)
|
||||
c.aead = NewAEAD(c.cipher, c.baseKey, data[5:5+32], c.peerRandom)
|
||||
c.nonce = make([]byte, 12)
|
||||
c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5])
|
||||
} else {
|
||||
@@ -291,7 +297,7 @@ func (c *ServerConn) Write(b []byte) (int, error) {
|
||||
EncodeHeader(data, 23, len(b)+16)
|
||||
c.aead.Seal(data[:5], c.nonce, b, data[:5])
|
||||
if bytes.Equal(c.nonce, MaxNonce) {
|
||||
c.aead = NewAead(c.cipher, c.baseKey, data[5:], data[:5])
|
||||
c.aead = NewAEAD(c.cipher, c.baseKey, data[5:], data[:5])
|
||||
}
|
||||
}
|
||||
IncreaseNonce(c.nonce)
|
||||
|
@@ -3,13 +3,21 @@ package encryption
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdh"
|
||||
"crypto/hkdf"
|
||||
"crypto/rand"
|
||||
"crypto/sha3"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
)
|
||||
|
||||
type XorConn struct {
|
||||
net.Conn
|
||||
Divide bool
|
||||
|
||||
head []byte
|
||||
key []byte
|
||||
ctr cipher.Stream
|
||||
peerCtr cipher.Stream
|
||||
@@ -25,8 +33,55 @@ type XorConn struct {
|
||||
in_skip int
|
||||
}
|
||||
|
||||
func NewXorConn(conn net.Conn, key []byte) *XorConn {
|
||||
return &XorConn{Conn: conn, key: key}
|
||||
func NewCTR(key, iv []byte, isServer bool) cipher.Stream {
|
||||
info := "CLIENT"
|
||||
if isServer {
|
||||
info = "SERVER" // avoids attackers sending traffic back to the client, though the encryption layer has its own protection
|
||||
}
|
||||
key, _ = hkdf.Key(sha3.New256, key, iv, info, 32) // avoids using pKey directly if attackers sent the basepoint, or whaterver they like
|
||||
block, _ := aes.NewCipher(key)
|
||||
return cipher.NewCTR(block, iv)
|
||||
}
|
||||
|
||||
func NewXorConn(conn net.Conn, mode uint32, pKey *ecdh.PublicKey, sKey *ecdh.PrivateKey) (*XorConn, error) {
|
||||
if mode == 0 || (pKey == nil && sKey == nil) || (pKey != nil && sKey != nil) {
|
||||
return nil, errors.New("invalid parameters")
|
||||
}
|
||||
c := &XorConn{
|
||||
Conn: conn,
|
||||
Divide: mode == 1,
|
||||
isHeader: true,
|
||||
out_header: make([]byte, 0, 5), // important
|
||||
in_header: make([]byte, 0, 5), // important
|
||||
}
|
||||
if pKey != nil {
|
||||
c.head = make([]byte, 16+32)
|
||||
rand.Read(c.head)
|
||||
eSKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
NewCTR(pKey.Bytes(), c.head[:16], false).XORKeyStream(c.head[16:], eSKey.PublicKey().Bytes()) // make X25519 public key distinguishable from random bytes
|
||||
c.key, _ = eSKey.ECDH(pKey)
|
||||
c.ctr = NewCTR(c.key, c.head[:16], false)
|
||||
}
|
||||
if sKey != nil {
|
||||
peerHead := make([]byte, 16+32)
|
||||
if _, err := io.ReadFull(c.Conn, peerHead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
NewCTR(sKey.PublicKey().Bytes(), peerHead[:16], false).XORKeyStream(peerHead[16:], peerHead[16:]) // we don't use buggy elligator, because we have PSK :)
|
||||
ePKey, err := ecdh.X25519().NewPublicKey(peerHead[16:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := sKey.ECDH(ePKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.peerCtr = NewCTR(key, peerHead[:16], false)
|
||||
c.head = make([]byte, 16)
|
||||
rand.Read(c.head) // make sure the server always replies random bytes even when received replays, though it is not important
|
||||
c.ctr = NewCTR(key, c.head, true) // the same key links the upload & download, though the encryption layer has its own link
|
||||
}
|
||||
return c, nil
|
||||
//chacha20.NewUnauthenticatedCipher()
|
||||
}
|
||||
|
||||
@@ -35,13 +90,6 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records
|
||||
return 0, nil
|
||||
}
|
||||
if !c.out_after0 {
|
||||
var iv []byte
|
||||
if c.ctr == nil {
|
||||
block, _ := aes.NewCipher(c.key)
|
||||
iv = make([]byte, 16)
|
||||
rand.Read(iv)
|
||||
c.ctr = cipher.NewCTR(block, iv)
|
||||
}
|
||||
t, l, _ := DecodeHeader(b)
|
||||
if t == 23 { // single 23
|
||||
l = 5
|
||||
@@ -49,20 +97,24 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records
|
||||
l += 10
|
||||
if t == 0 {
|
||||
c.out_after0 = true
|
||||
c.out_header = make([]byte, 0, 5) // important
|
||||
if c.Divide {
|
||||
l -= 5
|
||||
}
|
||||
}
|
||||
}
|
||||
c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b
|
||||
if iv != nil {
|
||||
b = append(iv, b...)
|
||||
l = len(b)
|
||||
if c.head != nil {
|
||||
b = append(c.head, b...)
|
||||
c.head = nil
|
||||
}
|
||||
if _, err := c.Conn.Write(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if iv != nil {
|
||||
b = b[16:] // for len(b)
|
||||
}
|
||||
return len(b), nil
|
||||
return l, nil
|
||||
}
|
||||
if c.Divide {
|
||||
return c.Conn.Write(b)
|
||||
}
|
||||
for p := b; ; { // for XTLS
|
||||
if len(p) <= c.out_skip {
|
||||
@@ -93,14 +145,12 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes...
|
||||
return 0, nil
|
||||
}
|
||||
if !c.in_after0 || !c.isHeader {
|
||||
if c.peerCtr == nil {
|
||||
if c.peerCtr == nil { // for client
|
||||
peerIv := make([]byte, 16)
|
||||
if _, err := io.ReadFull(c.Conn, peerIv); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
block, _ := aes.NewCipher(c.key)
|
||||
c.peerCtr = cipher.NewCTR(block, peerIv)
|
||||
c.isHeader = true
|
||||
c.peerCtr = NewCTR(c.key, peerIv, true)
|
||||
}
|
||||
if _, err := io.ReadFull(c.Conn, b); err != nil {
|
||||
return 0, err
|
||||
@@ -117,7 +167,6 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes...
|
||||
c.isHeader = false
|
||||
if t == 0 {
|
||||
c.in_after0 = true
|
||||
c.in_header = make([]byte, 0, 5) // important
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -125,6 +174,9 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes...
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
if c.Divide {
|
||||
return c.Conn.Read(b)
|
||||
}
|
||||
n, err := c.Conn.Read(b)
|
||||
for p := b[:n]; ; { // for XTLS
|
||||
if len(p) <= c.in_skip {
|
||||
|
Reference in New Issue
Block a user