Files
Xray-core/proxy/vless/encryption/xor.go

201 lines
4.8 KiB
Go

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
isHeader bool
skipNext bool
out_after0 bool
out_header []byte
out_skip int
in_after0 bool
in_header []byte
in_skip int
}
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().NewPrivateKey(c.head[16:])
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()
}
func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records
if len(b) == 0 {
return 0, nil
}
if !c.out_after0 {
t, l, _ := DecodeHeader(b)
if t == 23 { // single 23
l = 5
} else { // 1/0 + 23, or noises only
l += 10
if t == 0 {
c.out_after0 = true
if c.Divide {
l -= 5
}
}
}
c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard 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
}
return l, nil
}
if c.Divide {
return c.Conn.Write(b)
}
for p := b; ; { // for XTLS
if len(p) <= c.out_skip {
c.out_skip -= len(p)
break
}
p = p[c.out_skip:]
c.out_skip = 0
need := 5 - len(c.out_header)
if len(p) < need {
c.out_header = append(c.out_header, p...)
c.ctr.XORKeyStream(p, p)
break
}
_, c.out_skip, _ = DecodeHeader(append(c.out_header, p[:need]...))
c.out_header = c.out_header[:0]
c.ctr.XORKeyStream(p[:need], p[:need])
p = p[need:]
}
if _, err := c.Conn.Write(b); err != nil {
return 0, err
}
return len(b), nil
}
func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes...
if len(b) == 0 {
return 0, nil
}
if !c.in_after0 || !c.isHeader {
if c.peerCtr == nil { // for client
peerIv := make([]byte, 16)
if _, err := io.ReadFull(c.Conn, peerIv); err != nil {
return 0, err
}
c.peerCtr = NewCTR(c.key, peerIv, true)
}
if _, err := io.ReadFull(c.Conn, b); err != nil {
return 0, err
}
if c.skipNext {
c.skipNext = false
return len(b), nil
}
c.peerCtr.XORKeyStream(b, b)
if c.isHeader { // always 5-bytes
if t, _, _ := DecodeHeader(b); t == 23 {
c.skipNext = true
} else {
c.isHeader = false
if t == 0 {
c.in_after0 = true
}
}
} else {
c.isHeader = true
}
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 {
c.in_skip -= len(p)
break
}
p = p[c.in_skip:]
c.in_skip = 0
need := 5 - len(c.in_header)
if len(p) < need {
c.peerCtr.XORKeyStream(p, p)
c.in_header = append(c.in_header, p...)
break
}
c.peerCtr.XORKeyStream(p[:need], p[:need])
_, c.in_skip, _ = DecodeHeader(append(c.in_header, p[:need]...))
c.in_header = c.in_header[:0]
p = p[need:]
}
return n, err
}