mirror of
https://github.com/XTLS/Xray-core.git
synced 2025-08-22 01:26:49 +08:00
201 lines
4.8 KiB
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().GenerateKey(rand.Reader)
|
|
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
|
|
}
|