mirror of https://github.com/XTLS/Xray-core
				
				
				
			
		
			
				
	
	
		
			211 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			Go
		
	
	
package encryption
 | 
						|
 | 
						|
import (
 | 
						|
	"crypto/cipher"
 | 
						|
	"crypto/ecdh"
 | 
						|
	"crypto/mlkem"
 | 
						|
	"crypto/rand"
 | 
						|
	"io"
 | 
						|
	"net"
 | 
						|
	"sync"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/xtls/xray-core/common/errors"
 | 
						|
	"github.com/xtls/xray-core/common/protocol"
 | 
						|
	"lukechampine.com/blake3"
 | 
						|
)
 | 
						|
 | 
						|
type ClientInstance struct {
 | 
						|
	NfsPKeys      []any
 | 
						|
	NfsPKeysBytes [][]byte
 | 
						|
	Hash32s       [][32]byte
 | 
						|
	RelaysLength  int
 | 
						|
	XorMode       uint32
 | 
						|
	Seconds       uint32
 | 
						|
	PaddingLens   [][2]int
 | 
						|
	PaddingGaps   [][2]int
 | 
						|
 | 
						|
	RWLock sync.RWMutex
 | 
						|
	Expire time.Time
 | 
						|
	PfsKey []byte
 | 
						|
	Ticket []byte
 | 
						|
}
 | 
						|
 | 
						|
func (i *ClientInstance) Init(nfsPKeysBytes [][]byte, xorMode, seconds uint32, padding string) (err error) {
 | 
						|
	if i.NfsPKeys != nil {
 | 
						|
		return errors.New("already initialized")
 | 
						|
	}
 | 
						|
	l := len(nfsPKeysBytes)
 | 
						|
	if l == 0 {
 | 
						|
		return errors.New("empty nfsPKeysBytes")
 | 
						|
	}
 | 
						|
	i.NfsPKeys = make([]any, l)
 | 
						|
	i.NfsPKeysBytes = nfsPKeysBytes
 | 
						|
	i.Hash32s = make([][32]byte, l)
 | 
						|
	for j, k := range nfsPKeysBytes {
 | 
						|
		if len(k) == 32 {
 | 
						|
			if i.NfsPKeys[j], err = ecdh.X25519().NewPublicKey(k); err != nil {
 | 
						|
				return
 | 
						|
			}
 | 
						|
			i.RelaysLength += 32 + 32
 | 
						|
		} else {
 | 
						|
			if i.NfsPKeys[j], err = mlkem.NewEncapsulationKey768(k); err != nil {
 | 
						|
				return
 | 
						|
			}
 | 
						|
			i.RelaysLength += 1088 + 32
 | 
						|
		}
 | 
						|
		i.Hash32s[j] = blake3.Sum256(k)
 | 
						|
	}
 | 
						|
	i.RelaysLength -= 32
 | 
						|
	i.XorMode = xorMode
 | 
						|
	i.Seconds = seconds
 | 
						|
	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, protocol.HasAESGCMHardwareSupport)
 | 
						|
 | 
						|
	ivAndRealysLength := 16 + i.RelaysLength
 | 
						|
	pfsKeyExchangeLength := 18 + 1184 + 32 + 16
 | 
						|
	paddingLength, paddingLens, paddingGaps := CreatPadding(i.PaddingLens, i.PaddingGaps)
 | 
						|
	clientHello := make([]byte, ivAndRealysLength+pfsKeyExchangeLength+paddingLength)
 | 
						|
 | 
						|
	iv := clientHello[:16]
 | 
						|
	rand.Read(iv)
 | 
						|
	relays := clientHello[16:ivAndRealysLength]
 | 
						|
	var nfsKey []byte
 | 
						|
	var lastCTR cipher.Stream
 | 
						|
	for j, k := range i.NfsPKeys {
 | 
						|
		var index = 32
 | 
						|
		if k, ok := k.(*ecdh.PublicKey); ok {
 | 
						|
			privateKey, _ := ecdh.X25519().GenerateKey(rand.Reader)
 | 
						|
			copy(relays, privateKey.PublicKey().Bytes())
 | 
						|
			var err error
 | 
						|
			nfsKey, err = privateKey.ECDH(k)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if k, ok := k.(*mlkem.EncapsulationKey768); ok {
 | 
						|
			var ciphertext []byte
 | 
						|
			nfsKey, ciphertext = k.Encapsulate()
 | 
						|
			copy(relays, ciphertext)
 | 
						|
			index = 1088
 | 
						|
		}
 | 
						|
		if i.XorMode > 0 { // this xor can (others can't) be recovered by client's config, revealing an X25519 public key / ML-KEM-768 ciphertext, that's why "native" values
 | 
						|
			NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // make X25519 public key / ML-KEM-768 ciphertext distinguishable from random bytes
 | 
						|
		}
 | 
						|
		if lastCTR != nil {
 | 
						|
			lastCTR.XORKeyStream(relays, relays[:32]) // make this relay irreplaceable
 | 
						|
		}
 | 
						|
		if j == len(i.NfsPKeys)-1 {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		lastCTR = NewCTR(nfsKey, iv)
 | 
						|
		lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:])
 | 
						|
		relays = relays[index+32:]
 | 
						|
	}
 | 
						|
	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
 | 
						|
			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.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)
 | 
						|
			}
 | 
						|
			return c, nil
 | 
						|
		}
 | 
						|
		i.RWLock.RUnlock()
 | 
						|
	}
 | 
						|
 | 
						|
	pfsKeyExchange := clientHello[ivAndRealysLength : ivAndRealysLength+pfsKeyExchangeLength]
 | 
						|
	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()...)
 | 
						|
	nfsAEAD.Seal(pfsKeyExchange[:18], nil, pfsPublicKey, nil)
 | 
						|
 | 
						|
	padding := clientHello[ivAndRealysLength+pfsKeyExchangeLength:]
 | 
						|
	nfsAEAD.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil)
 | 
						|
	nfsAEAD.Seal(padding[:18], nil, padding[18:paddingLength-16], nil)
 | 
						|
 | 
						|
	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])
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	encryptedPfsPublicKey := make([]byte, 1088+32+16)
 | 
						|
	if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	nfsAEAD.Open(encryptedPfsPublicKey[:0], MaxNonce, encryptedPfsPublicKey, nil)
 | 
						|
	mlkem768Key, err := mlkem768DKey.Decapsulate(encryptedPfsPublicKey[:1088])
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	peerX25519PKey, err := ecdh.X25519().NewPublicKey(encryptedPfsPublicKey[1088 : 1088+32])
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	x25519Key, err := x25519SKey.ECDH(peerX25519PKey)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	pfsKey := make([]byte, 32+32) // no more capacity
 | 
						|
	copy(pfsKey, mlkem768Key)
 | 
						|
	copy(pfsKey[32:], x25519Key)
 | 
						|
	c.UnitedKey = append(pfsKey, nfsKey...)
 | 
						|
	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.PeerAEAD.Open(encryptedTicket[:0], nil, encryptedTicket, nil); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	seconds := DecodeLength(encryptedTicket)
 | 
						|
 | 
						|
	if i.Seconds > 0 && seconds > 0 {
 | 
						|
		i.RWLock.Lock()
 | 
						|
		i.Expire = time.Now().Add(time.Duration(seconds) * time.Second)
 | 
						|
		i.PfsKey = pfsKey
 | 
						|
		i.Ticket = encryptedTicket[:16]
 | 
						|
		i.RWLock.Unlock()
 | 
						|
	}
 | 
						|
 | 
						|
	encryptedLength := make([]byte, 18)
 | 
						|
	if _, err := io.ReadFull(conn, encryptedLength); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if _, err := c.PeerAEAD.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	length := DecodeLength(encryptedLength[:2])
 | 
						|
	c.PeerPadding = make([]byte, length) // important: allows server sends padding slowly, eliminating 1-RTT's traffic pattern
 | 
						|
 | 
						|
	if i.XorMode == 2 {
 | 
						|
		c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), NewCTR(c.UnitedKey, encryptedTicket[:16]), 0, length)
 | 
						|
	}
 | 
						|
	return c, nil
 | 
						|
}
 |