| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- 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 [][3]int
- PaddingGaps [][3]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
- }
|