2025-08-28 04:55:36 +00:00
package encryption
import (
"crypto/cipher"
"crypto/ecdh"
"crypto/mlkem"
"crypto/rand"
"io"
"net"
"sync"
"time"
"github.com/xtls/xray-core/common/errors"
2025-08-29 14:05:39 +00:00
"github.com/xtls/xray-core/common/protocol"
2025-08-28 04:55:36 +00:00
"lukechampine.com/blake3"
)
type ClientInstance struct {
NfsPKeys [] any
NfsPKeysBytes [][] byte
Hash32s [][ 32 ] byte
RelaysLength int
XorMode uint32
Seconds uint32
2025-08-31 11:35:38 +00:00
PaddingLens [][ 3 ] int
PaddingGaps [][ 3 ] int
2025-08-28 04:55:36 +00:00
RWLock sync . RWMutex
Expire time . Time
PfsKey [] byte
Ticket [] byte
}
2025-08-31 04:09:28 +00:00
func ( i * ClientInstance ) Init ( nfsPKeysBytes [][] byte , xorMode , seconds uint32 , padding string ) ( err error ) {
2025-08-28 04:55:36 +00:00
if i . NfsPKeys != nil {
2025-08-31 04:09:28 +00:00
return errors . New ( "already initialized" )
2025-08-28 04:55:36 +00:00
}
l := len ( nfsPKeysBytes )
if l == 0 {
2025-08-31 04:09:28 +00:00
return errors . New ( "empty nfsPKeysBytes" )
2025-08-28 04:55:36 +00:00
}
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
2025-08-31 04:09:28 +00:00
return ParsePadding ( padding , & i . PaddingLens , & i . PaddingGaps )
2025-08-28 04:55:36 +00:00
}
func ( i * ClientInstance ) Handshake ( conn net . Conn ) ( * CommonConn , error ) {
if i . NfsPKeys == nil {
return nil , errors . New ( "uninitialized" )
}
2025-08-29 14:05:39 +00:00
c := NewCommonConn ( conn , protocol . HasAESGCMHardwareSupport )
2025-08-28 04:55:36 +00:00
ivAndRealysLength := 16 + i . RelaysLength
pfsKeyExchangeLength := 18 + 1184 + 32 + 16
2025-08-31 04:09:28 +00:00
paddingLength , paddingLens , paddingGaps := CreatPadding ( i . PaddingLens , i . PaddingGaps )
2025-08-28 04:55:36 +00:00
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 {
2026-05-29 23:04:59 +08:00
index := 32
2025-08-28 04:55:36 +00:00
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 :]
}
2025-08-29 14:05:39 +00:00
nfsAEAD := NewAEAD ( iv , nfsKey , c . UseAES )
2025-08-28 04:55:36 +00:00
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
2025-08-29 14:05:39 +00:00
nfsAEAD . Seal ( clientHello [: ivAndRealysLength ], nil , EncodeLength ( 32 ), nil )
nfsAEAD . Seal ( clientHello [: ivAndRealysLength + 18 ], nil , i . Ticket , nil )
2025-08-28 04:55:36 +00:00
i . RWLock . RUnlock ()
c . PreWrite = clientHello [: ivAndRealysLength + 18 + 32 ]
2025-08-29 14:05:39 +00:00
c . AEAD = NewAEAD ( clientHello [ ivAndRealysLength + 18 : ivAndRealysLength + 18 + 32 ], c . UnitedKey , c . UseAES )
2025-08-28 04:55:36 +00:00
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 ]
2025-08-29 14:05:39 +00:00
nfsAEAD . Seal ( pfsKeyExchange [: 0 ], nil , EncodeLength ( pfsKeyExchangeLength - 18 ), nil )
2025-08-28 04:55:36 +00:00
mlkem768DKey , _ := mlkem . GenerateKey768 ()
x25519SKey , _ := ecdh . X25519 (). GenerateKey ( rand . Reader )
pfsPublicKey := append ( mlkem768DKey . EncapsulationKey (). Bytes (), x25519SKey . PublicKey (). Bytes () ... )
2025-08-29 14:05:39 +00:00
nfsAEAD . Seal ( pfsKeyExchange [: 18 ], nil , pfsPublicKey , nil )
2025-08-28 04:55:36 +00:00
padding := clientHello [ ivAndRealysLength + pfsKeyExchangeLength :]
2025-08-29 14:05:39 +00:00
nfsAEAD . Seal ( padding [: 0 ], nil , EncodeLength ( paddingLength - 18 ), nil )
nfsAEAD . Seal ( padding [: 18 ], nil , padding [ 18 : paddingLength - 16 ], nil )
2025-08-28 04:55:36 +00:00
2025-08-31 04:09:28 +00:00
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 ])
}
2025-08-28 04:55:36 +00:00
}
encryptedPfsPublicKey := make ([] byte , 1088 + 32 + 16 )
if _ , err := io . ReadFull ( conn , encryptedPfsPublicKey ); err != nil {
return nil , err
}
2025-08-29 14:05:39 +00:00
nfsAEAD . Open ( encryptedPfsPublicKey [: 0 ], MaxNonce , encryptedPfsPublicKey , nil )
2025-08-28 04:55:36 +00:00
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 ... )
2025-08-29 14:05:39 +00:00
c . AEAD = NewAEAD ( pfsPublicKey , c . UnitedKey , c . UseAES )
c . PeerAEAD = NewAEAD ( encryptedPfsPublicKey [: 1088 + 32 ], c . UnitedKey , c . UseAES )
2025-08-28 04:55:36 +00:00
encryptedTicket := make ([] byte , 32 )
if _ , err := io . ReadFull ( conn , encryptedTicket ); err != nil {
return nil , err
}
2025-08-29 14:05:39 +00:00
if _ , err := c . PeerAEAD . Open ( encryptedTicket [: 0 ], nil , encryptedTicket , nil ); err != nil {
2025-08-28 04:55:36 +00:00
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
}
2025-08-29 14:05:39 +00:00
if _ , err := c . PeerAEAD . Open ( encryptedLength [: 0 ], nil , encryptedLength , nil ); err != nil {
2025-08-28 04:55:36 +00:00
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
}