diff --git a/transport/vless/encryption/client.go b/transport/vless/encryption/client.go index 875ace2e..b504e153 100644 --- a/transport/vless/encryption/client.go +++ b/transport/vless/encryption/client.go @@ -75,14 +75,13 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { iv := clientHello[:16] rand.Read(iv) relays := clientHello[16:ivAndRealysLength] - var nfsPublicKey, nfsKey []byte + 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) - nfsPublicKey = privateKey.PublicKey().Bytes() - copy(relays, nfsPublicKey) + copy(relays, privateKey.PublicKey().Bytes()) var err error nfsKey, err = privateKey.ECDH(k) if err != nil { @@ -90,11 +89,12 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { } } if k, ok := k.(*mlkem.EncapsulationKey768); ok { - nfsKey, nfsPublicKey = k.Encapsulate() - copy(relays, nfsPublicKey) + var ciphertext []byte + nfsKey, ciphertext = k.Encapsulate() + copy(relays, ciphertext) index = 1088 } - if i.XorMode > 0 { // this xor can (others can't) be decrypted by client's config, revealing an X25519 public key / ML-KEM-768 ciphertext, but it is not important + 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 { @@ -107,20 +107,20 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { lastCTR.XORKeyStream(relays[index:], i.Hash32s[j+1][:]) relays = relays[index+32:] } - nfsGCM := NewGCM(nfsPublicKey, nfsKey) + nfsGCM := NewGCM(iv, nfsKey) if i.Seconds > 0 { i.RWLock.RLock() if time.Now().Before(i.Expire) { c.Client = i - c.UnitedKey = append(i.PfsKey, nfsKey...) + c.UnitedKey = append(i.PfsKey, nfsKey...) // different unitedKey for each connection nfsGCM.Seal(clientHello[:ivAndRealysLength], nil, EncodeLength(32), nil) nfsGCM.Seal(clientHello[:ivAndRealysLength+18], nil, i.Ticket, nil) i.RWLock.RUnlock() c.PreWrite = clientHello[:ivAndRealysLength+18+32] c.GCM = NewGCM(clientHello[ivAndRealysLength+18:ivAndRealysLength+18+32], c.UnitedKey) if i.XorMode == 2 { - c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 32) + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), nil, len(c.PreWrite), 16) } return c, nil } @@ -141,21 +141,9 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := conn.Write(clientHello); err != nil { return nil, err } - // padding can be sent in a fragmented way, to create variable traffic pattern, before VLESS flow takes control + // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control - encryptedLength := make([]byte, 18) - if _, err := io.ReadFull(conn, encryptedLength); err != nil { - return nil, err - } - if _, err := nfsGCM.Open(encryptedLength[:0], make([]byte, 12), encryptedLength, nil); err != nil { - return nil, err - } - length := DecodeLength(encryptedLength[:2]) - - if length < 1088+32+16 { // server may send more public keys - return nil, errors.New("too short length") - } - encryptedPfsPublicKey := make([]byte, length) + encryptedPfsPublicKey := make([]byte, 1088+32+16) if _, err := io.ReadFull(conn, encryptedPfsPublicKey); err != nil { return nil, err } @@ -194,22 +182,18 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*CommonConn, error) { i.RWLock.Unlock() } + encryptedLength := make([]byte, 18) if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err } if _, err := c.PeerGCM.Open(encryptedLength[:0], nil, encryptedLength, nil); err != nil { return nil, err } - encryptedPadding := make([]byte, DecodeLength(encryptedLength[:2])) // TODO: move to Read() - if _, err := io.ReadFull(conn, encryptedPadding); err != nil { - return nil, err - } - if _, err := c.PeerGCM.Open(encryptedPadding[:0], nil, encryptedPadding, 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, 0) + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, iv), NewCTR(c.UnitedKey, encryptedTicket[:16]), 0, length) } return c, nil } diff --git a/transport/vless/encryption/common.go b/transport/vless/encryption/common.go index 3e4c1ec3..8e78d486 100644 --- a/transport/vless/encryption/common.go +++ b/transport/vless/encryption/common.go @@ -18,12 +18,13 @@ import ( type CommonConn struct { net.Conn - Client *ClientInstance - UnitedKey []byte - PreWrite []byte - GCM *GCM - PeerGCM *GCM - input bytes.Reader // PeerCache + Client *ClientInstance + UnitedKey []byte + PreWrite []byte + GCM *GCM + PeerPadding []byte + PeerGCM *GCM + input bytes.Reader // PeerCache } func (c *CommonConn) Write(b []byte) (int, error) { @@ -39,12 +40,12 @@ func (c *CommonConn) Write(b []byte) (int, error) { n += len(b) data = make([]byte, 5+len(b)+16) EncodeHeader(data, len(b)+16) - aead := c.GCM + max := false if bytes.Equal(c.GCM.Nonce[:], MaxNonce) { - aead = nil + max = true } c.GCM.Seal(data[:5], nil, b, data[:5]) - if aead == nil { + if max { c.GCM = NewGCM(data[5:], c.UnitedKey) } if c.PreWrite != nil { @@ -63,15 +64,24 @@ func (c *CommonConn) Read(b []byte) (int, error) { return 0, nil } if c.PeerGCM == nil { // client's 0-RTT - serverRandom := make([]byte, 32) + serverRandom := make([]byte, 16) if _, err := io.ReadFull(c.Conn, serverRandom); err != nil { return 0, err } c.PeerGCM = NewGCM(serverRandom, c.UnitedKey) if xorConn, ok := c.Conn.(*XorConn); ok { - xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom[16:]) + xorConn.PeerCTR = NewCTR(c.UnitedKey, serverRandom) } } + if c.PeerPadding != nil { // client's 1-RTT + if _, err := io.ReadFull(c.Conn, c.PeerPadding); err != nil { + return 0, err + } + if _, err := c.PeerGCM.Open(c.PeerPadding[:0], nil, c.PeerPadding, nil); err != nil { + return 0, err + } + c.PeerPadding = nil + } if c.input.Len() > 0 { return c.input.Read(b) } @@ -96,13 +106,13 @@ func (c *CommonConn) Read(b []byte) (int, error) { if len(dst) <= len(b) { dst = b[:len(dst)] // avoids another copy() } - var peerAEAD *GCM + var newGCM *GCM if bytes.Equal(c.PeerGCM.Nonce[:], MaxNonce) { - peerAEAD = NewGCM(peerData, c.UnitedKey) + newGCM = NewGCM(peerData, c.UnitedKey) } _, err = c.PeerGCM.Open(dst[:0], nil, peerData, h) - if peerAEAD != nil { - c.PeerGCM = peerAEAD + if newGCM != nil { + c.PeerGCM = newGCM } if err != nil { return 0, err diff --git a/transport/vless/encryption/doc.go b/transport/vless/encryption/doc.go index eb46fde2..d2eb2753 100644 --- a/transport/vless/encryption/doc.go +++ b/transport/vless/encryption/doc.go @@ -18,4 +18,5 @@ // https://github.com/XTLS/Xray-core/commit/38cc306c955c362f044e074049a5e67b6b9fb389 // https://github.com/XTLS/Xray-core/commit/b33555cc0a52d0af3c23d2af8fca42f8a685d9af // https://github.com/XTLS/Xray-core/commit/ad7140641c44239c9dcdc3d7215ea639b1f0841c +// https://github.com/XTLS/Xray-core/commit/0199dea39988a1a1b846d0bf8598631bade40902 package encryption diff --git a/transport/vless/encryption/server.go b/transport/vless/encryption/server.go index 69210c38..0bb5fa60 100644 --- a/transport/vless/encryption/server.go +++ b/transport/vless/encryption/server.go @@ -109,7 +109,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { } iv := ivAndRelays[:16] relays := ivAndRelays[16:] - var nfsPublicKey, nfsKey []byte + var nfsKey []byte var lastCTR cipher.Stream for j, k := range i.NfsSKeys { if lastCTR != nil { @@ -122,9 +122,8 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if i.XorMode > 0 { NewCTR(i.NfsPKeysBytes[j], iv).XORKeyStream(relays, relays[:index]) // we don't use buggy elligator, because we have PSK :) } - nfsPublicKey = relays[:index] if k, ok := k.(*ecdh.PrivateKey); ok { - publicKey, err := ecdh.X25519().NewPublicKey(nfsPublicKey) + publicKey, err := ecdh.X25519().NewPublicKey(relays[:index]) if err != nil { return nil, err } @@ -135,7 +134,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { } if k, ok := k.(*mlkem.DecapsulationKey768); ok { var err error - nfsKey, err = k.Decapsulate(nfsPublicKey) + nfsKey, err = k.Decapsulate(relays[:index]) if err != nil { return nil, err } @@ -151,7 +150,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { } relays = relays[32:] } - nfsGCM := NewGCM(nfsPublicKey, nfsKey) + nfsGCM := NewGCM(iv, nfsKey) encryptedLength := make([]byte, 18) if _, err := io.ReadFull(conn, encryptedLength); err != nil { @@ -187,16 +186,16 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { conn.Write(noises) // make client do new handshake return nil, errors.New("expired ticket") } - if _, replay := s.Replays.LoadOrStore([32]byte(encryptedTicket), true); replay { + if _, replay := s.Replays.LoadOrStore([32]byte(nfsKey), true); replay { // prevents bad client also return nil, errors.New("replay detected") } - c.UnitedKey = append(s.PfsKey, nfsKey...) // the same key links the upload & download - c.PreWrite = make([]byte, 32) // always trust yourself, not the client + c.UnitedKey = append(s.PfsKey, nfsKey...) // the same nfsKey links the upload & download + c.PreWrite = make([]byte, 16) // always trust yourself, not the client rand.Read(c.PreWrite) c.GCM = NewGCM(c.PreWrite, c.UnitedKey) - c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) + c.PeerGCM = NewGCM(encryptedTicket, c.UnitedKey) // unchangeable ctx, and different ctx length for upload / download if i.XorMode == 2 { - c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite[16:]), NewCTR(c.UnitedKey, iv), 32, 0) + c.Conn = NewXorConn(conn, NewCTR(c.UnitedKey, c.PreWrite), NewCTR(c.UnitedKey, iv), 16, 0) // it doesn't matter if the attacker sends client's iv back to the client } return c, nil } @@ -234,12 +233,11 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { rand.Read(ticket) copy(ticket, EncodeLength(int(i.Seconds*4/5))) - pfsKeyExchangeLength := 18 + 1088 + 32 + 16 + pfsKeyExchangeLength := 1088 + 32 + 16 encryptedTicketLength := 32 paddingLength := int(randBetween(100, 1000)) serverHello := make([]byte, pfsKeyExchangeLength+encryptedTicketLength+paddingLength) - nfsGCM.Seal(serverHello[:0], make([]byte, 12), EncodeLength(pfsKeyExchangeLength-18), nil) // it is safe because our nonce starts from 1 - nfsGCM.Seal(serverHello[:18], MaxNonce, pfsPublicKey, nil) + nfsGCM.Seal(serverHello[:0], MaxNonce, pfsPublicKey, nil) c.GCM.Seal(serverHello[:pfsKeyExchangeLength], nil, ticket, nil) padding := serverHello[pfsKeyExchangeLength+encryptedTicketLength:] c.GCM.Seal(padding[:0], nil, EncodeLength(paddingLength-18), nil) @@ -248,7 +246,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { if _, err := conn.Write(serverHello); err != nil { return nil, err } - // padding can be sent in a fragmented way, to create variable traffic pattern, before VLESS flow takes control + // padding can be sent in a fragmented way, to create variable traffic pattern, before inner VLESS flow takes control if i.Seconds > 0 { i.RWLock.Lock() @@ -259,6 +257,7 @@ func (i *ServerInstance) Handshake(conn net.Conn) (*CommonConn, error) { i.RWLock.Unlock() } + // important: allows client sends padding slowly, eliminating 1-RTT's traffic pattern if _, err := io.ReadFull(conn, encryptedLength); err != nil { return nil, err }