mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-23 09:30:27 +00:00
https://github.com/XTLS/Xray-core/pull/6487#issuecomment-5092235757 --------- Co-authored-by: 风扇滑翔翼 <Fangliding.fshxy@outlook.com>
288 lines
6.7 KiB
Go
288 lines
6.7 KiB
Go
package xmc
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"net"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type clientConn struct {
|
|
reader io.Reader
|
|
writer io.Writer
|
|
c net.Conn
|
|
|
|
state clientState
|
|
|
|
handshakeLock sync.Mutex
|
|
lifecycleMu sync.Mutex
|
|
closed bool
|
|
profiles []loginProfile
|
|
password string
|
|
rsaPublicKey []byte
|
|
hostname string
|
|
paddingSchedule []paddingTurn
|
|
packet *packetStream
|
|
deadlines *connectionDeadlines
|
|
}
|
|
|
|
type clientState int
|
|
|
|
var (
|
|
clientStateHandshake clientState = 1
|
|
clientStateProxy clientState = 2
|
|
)
|
|
|
|
func newClientConn(c net.Conn, profiles []loginProfile, password string, rsaPublicKey []byte, hostname string) (*clientConn, error) {
|
|
if len(rsaPublicKey) == 0 {
|
|
return nil, fmt.Errorf("empty rsa public key")
|
|
}
|
|
if len(profiles) == 0 {
|
|
return nil, fmt.Errorf("empty profiles")
|
|
}
|
|
paddingSchedule, err := newClientPaddingSchedule2612()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("select padding profile: %w", err)
|
|
}
|
|
return &clientConn{
|
|
reader: bufio.NewReader(c),
|
|
writer: c,
|
|
c: c,
|
|
state: clientStateHandshake,
|
|
handshakeLock: sync.Mutex{},
|
|
profiles: profiles,
|
|
password: password,
|
|
rsaPublicKey: rsaPublicKey,
|
|
hostname: hostname,
|
|
paddingSchedule: paddingSchedule,
|
|
deadlines: newConnectionDeadlines(c),
|
|
}, nil
|
|
}
|
|
|
|
func (c *clientConn) handshake() error {
|
|
c.handshakeLock.Lock()
|
|
defer c.handshakeLock.Unlock()
|
|
|
|
if c.state != clientStateHandshake {
|
|
return nil
|
|
}
|
|
|
|
if err := c.deadlines.beginHandshake(); err != nil {
|
|
return fmt.Errorf("set deadline: %w", err)
|
|
}
|
|
defer func() { _ = c.deadlines.endHandshake() }()
|
|
|
|
var (
|
|
protocolVersion Varint = Varint(775)
|
|
serverAddress String = String(c.hostname)
|
|
serverPort UnsignedShort = UnsignedShort(25565)
|
|
nextState Varint = Varint(2)
|
|
)
|
|
|
|
host, portString, err := net.SplitHostPort(c.c.RemoteAddr().String())
|
|
if err == nil {
|
|
port, err := strconv.Atoi(portString)
|
|
if err == nil {
|
|
serverPort = UnsignedShort(port)
|
|
}
|
|
|
|
if serverAddress == "" {
|
|
serverAddress = String(host)
|
|
}
|
|
}
|
|
|
|
err = writePacket(c.writer, 0x00, &protocolVersion, &serverAddress, &serverPort, &nextState)
|
|
if err != nil {
|
|
return fmt.Errorf("write handshake packet: %w", err)
|
|
}
|
|
|
|
// Login Start
|
|
randomProfile, err := rand.Int(rand.Reader, big.NewInt(int64(len(c.profiles))))
|
|
if err != nil {
|
|
return fmt.Errorf("select profile: %w", err)
|
|
}
|
|
selectedProfile := c.profiles[randomProfile.Int64()]
|
|
username := String(selectedProfile.Username)
|
|
|
|
err = writePacket(c.writer, 0x00, &username, &selectedProfile.UUID)
|
|
if err != nil {
|
|
return fmt.Errorf("write login start: %w", err)
|
|
}
|
|
|
|
// Encryption Request
|
|
pkt, err := readPacket(c.reader)
|
|
if err != nil {
|
|
return fmt.Errorf("read encryption request: %w", err)
|
|
}
|
|
|
|
if pkt.packetID != 0x01 {
|
|
return fmt.Errorf("bad encrypt request packet id")
|
|
}
|
|
|
|
var (
|
|
serverId String
|
|
publicKey Bytes
|
|
verifyToken Bytes
|
|
)
|
|
|
|
err = pkt.readFields(&serverId, &publicKey, &verifyToken)
|
|
if err != nil {
|
|
return fmt.Errorf("read encryption request fields: %w", err)
|
|
}
|
|
|
|
if !bytes.Equal(publicKey, c.rsaPublicKey) {
|
|
return fmt.Errorf("server public key mismatch")
|
|
}
|
|
|
|
k, err := x509.ParsePKIXPublicKey(publicKey)
|
|
if err != nil {
|
|
return fmt.Errorf("parse server public key: %w", err)
|
|
}
|
|
|
|
rsaPublicKey, ok := k.(*rsa.PublicKey)
|
|
if !ok {
|
|
return fmt.Errorf("parse server public key: not rsa")
|
|
}
|
|
|
|
sharedSecret := make([]byte, 16)
|
|
if _, err = rand.Read(sharedSecret); err != nil {
|
|
return fmt.Errorf("generate shared secret: %w", err)
|
|
}
|
|
|
|
encryptedSharedSecret, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, sharedSecret)
|
|
if err != nil {
|
|
return fmt.Errorf("encrypt shared secret: %w", err)
|
|
}
|
|
|
|
verifyToken = append(verifyToken, []byte(c.password)...) // append pre-shared password
|
|
|
|
encryptedVerifyToken, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, verifyToken)
|
|
if err != nil {
|
|
return fmt.Errorf("encrypt verify token: %w", err)
|
|
}
|
|
|
|
// Send Encryption Response
|
|
err = writePacket(
|
|
c.writer,
|
|
0x01,
|
|
(*Bytes)(&encryptedSharedSecret),
|
|
(*Bytes)(&encryptedVerifyToken),
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("write encryption response: %w", err)
|
|
}
|
|
|
|
// Enable encryption
|
|
c.reader, err = newCryptoReader(c.reader, sharedSecret)
|
|
if err != nil {
|
|
return fmt.Errorf("new crypto reader: %w", err)
|
|
}
|
|
|
|
c.writer, err = newCryptoWriter(c.writer, sharedSecret)
|
|
if err != nil {
|
|
return fmt.Errorf("new crypto writer: %w", err)
|
|
}
|
|
|
|
pkt, err = readPacket(c.reader)
|
|
if err != nil {
|
|
return fmt.Errorf("read login finished: %w", err)
|
|
}
|
|
if pkt.packetID == 0x00 {
|
|
var reason String
|
|
if readErr := pkt.readFields(&reason); readErr != nil {
|
|
return fmt.Errorf("authentication rejected")
|
|
}
|
|
return fmt.Errorf("authentication rejected: %s", reason)
|
|
}
|
|
if pkt.packetID != 0x02 {
|
|
return fmt.Errorf("bad login finished packet id: %d", pkt.packetID)
|
|
}
|
|
|
|
receivedProfile, err := readLoginSuccess(pkt)
|
|
if err != nil {
|
|
return fmt.Errorf("read login finished fields: %w", err)
|
|
}
|
|
if receivedProfile != selectedProfile {
|
|
return fmt.Errorf("login profile mismatch")
|
|
}
|
|
loginAcknowledgedLength, err := writePacketWithLength(c.writer, 0x03)
|
|
if err != nil {
|
|
return fmt.Errorf("write login acknowledged: %w", err)
|
|
}
|
|
if err = runPaddingSchedule(c.reader, c.writer, true, loginAcknowledgedLength, c.paddingSchedule); err != nil {
|
|
return fmt.Errorf("run startup padding: %w", err)
|
|
}
|
|
|
|
packet := newPacketStream(c.reader, c.writer, true)
|
|
c.lifecycleMu.Lock()
|
|
if c.closed {
|
|
c.lifecycleMu.Unlock()
|
|
packet.Stop()
|
|
return net.ErrClosed
|
|
}
|
|
c.packet = packet
|
|
c.reader = packet
|
|
c.writer = packet
|
|
c.state = clientStateProxy
|
|
c.lifecycleMu.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *clientConn) Read(b []byte) (int, error) {
|
|
err := c.handshake()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("handshake: %w", err)
|
|
}
|
|
|
|
return c.reader.Read(b)
|
|
}
|
|
|
|
func (c *clientConn) Write(b []byte) (int, error) {
|
|
err := c.handshake()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("handshake: %w", err)
|
|
}
|
|
|
|
return c.writer.Write(b)
|
|
}
|
|
|
|
func (c *clientConn) Close() error {
|
|
c.lifecycleMu.Lock()
|
|
c.closed = true
|
|
packet := c.packet
|
|
c.lifecycleMu.Unlock()
|
|
if packet != nil {
|
|
packet.Stop()
|
|
}
|
|
return c.c.Close()
|
|
}
|
|
|
|
func (c *clientConn) LocalAddr() net.Addr {
|
|
return c.c.LocalAddr()
|
|
}
|
|
|
|
func (c *clientConn) RemoteAddr() net.Addr {
|
|
return c.c.RemoteAddr()
|
|
}
|
|
|
|
func (c *clientConn) SetDeadline(t time.Time) error {
|
|
return c.deadlines.setDeadline(t)
|
|
}
|
|
|
|
func (c *clientConn) SetReadDeadline(t time.Time) error {
|
|
return c.deadlines.setReadDeadline(t)
|
|
}
|
|
|
|
func (c *clientConn) SetWriteDeadline(t time.Time) error {
|
|
return c.deadlines.setWriteDeadline(t)
|
|
}
|