mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-08-04 15:19:13 +00:00
Merge b167850cef into 345c76f9a8
This commit is contained in:
commit
4a191bd8fd
12 changed files with 1624 additions and 0 deletions
|
|
@ -2,9 +2,11 @@ package conf
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/netip"
|
||||
|
|
@ -24,6 +26,7 @@ import (
|
|||
"github.com/xtls/xray-core/transport/internet"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/fragment"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/header/custom"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/minecraft"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/aes128gcm"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/header"
|
||||
"github.com/xtls/xray-core/transport/internet/finalmask/mkcp/original"
|
||||
|
|
@ -1248,6 +1251,7 @@ var (
|
|||
"header-custom": func() interface{} { return new(HeaderCustomTCP) },
|
||||
"fragment": func() interface{} { return new(FragmentMask) },
|
||||
"sudoku": func() interface{} { return new(Sudoku) },
|
||||
"minecraft": func() interface{} { return new(Minecraft) },
|
||||
}, "type", "settings")
|
||||
|
||||
udpmaskLoader = NewJSONConfigLoader(ConfigCreatorCache{
|
||||
|
|
@ -1468,6 +1472,40 @@ func (c *FragmentMask) Build() (proto.Message, error) {
|
|||
return config, nil
|
||||
}
|
||||
|
||||
type Minecraft struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Usernames []string `json:"usernames"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (c *Minecraft) Build() (proto.Message, error) {
|
||||
if len(c.Usernames) == 0 {
|
||||
c.Usernames = []string{"Dream"}
|
||||
}
|
||||
|
||||
if c.Password == "" {
|
||||
return nil, fmt.Errorf("empty password")
|
||||
}
|
||||
|
||||
rsaPrivateKey, err := minecraft.DeriveRSAKey(c.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("derive minecraft rsa key: %w", err)
|
||||
}
|
||||
|
||||
rsaPublicKey, err := x509.MarshalPKIXPublicKey(&rsaPrivateKey.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("marshal minecraft rsa public key: %w", err)
|
||||
}
|
||||
|
||||
return &minecraft.Config{
|
||||
Password: c.Password,
|
||||
Usernames: c.Usernames,
|
||||
Hostname: c.Hostname,
|
||||
RsaPrivateKey: x509.MarshalPKCS1PrivateKey(rsaPrivateKey),
|
||||
RsaPublicKey: rsaPublicKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type NoiseItem struct {
|
||||
Rand Int32Range `json:"rand"`
|
||||
RandRange *Int32Range `json:"randRange"`
|
||||
|
|
|
|||
138
transport/internet/finalmask/minecraft/cfb8.go
Normal file
138
transport/internet/finalmask/minecraft/cfb8.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package minecraft
|
||||
|
||||
// Copied from https://github.com/Tnze/go-mc/blob/539b4a3a7f030332eb58b8a946116ae7907630d2/net/CFB8/cfb8.go
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/subtle"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type cfb8 struct {
|
||||
c cipher.Block
|
||||
blockSize int
|
||||
ivPos int
|
||||
iv []byte
|
||||
de bool
|
||||
}
|
||||
|
||||
func newCFB8Decrypt(c cipher.Block, iv []byte) *cfb8 {
|
||||
return newCFB8(c, iv, true)
|
||||
}
|
||||
|
||||
func newCFB8Encrypt(c cipher.Block, iv []byte) *cfb8 {
|
||||
return newCFB8(c, iv, false)
|
||||
}
|
||||
|
||||
func newCFB8(c cipher.Block, iv []byte, de bool) *cfb8 {
|
||||
cp := make([]byte, len(iv)*3)
|
||||
copy(cp, iv)
|
||||
return &cfb8{
|
||||
c: c,
|
||||
blockSize: c.BlockSize(),
|
||||
iv: cp,
|
||||
de: de,
|
||||
}
|
||||
}
|
||||
|
||||
func (cf *cfb8) XORKeyStream(dst, src []byte) {
|
||||
if len(src) == 0 {
|
||||
return
|
||||
}
|
||||
if len(dst) < len(src) {
|
||||
panic("cfb8: output smaller than input")
|
||||
}
|
||||
|
||||
// If dst and src does not overlap in first block size,
|
||||
// and the length of src is greater than 2*blockSize,
|
||||
// we can use an optimized implementation.
|
||||
if len(src) > cf.blockSize<<1 &&
|
||||
(uintptr(unsafe.Pointer(&dst[0]))+uintptr(cf.blockSize) <= uintptr(unsafe.Pointer(&src[0])) ||
|
||||
uintptr(unsafe.Pointer(&src[0]))+uintptr(len(src)) <= uintptr(unsafe.Pointer(&dst[0]))) {
|
||||
// encrypt/decrypt first blockSize bytes
|
||||
// After this, the IV will come to the same as
|
||||
// the last blockSize of ciphertext, so
|
||||
// we can reuse them without copy.
|
||||
cf.xorKeyStream(dst, src[:cf.blockSize])
|
||||
var ciphertext []byte
|
||||
if cf.de {
|
||||
ciphertext = src
|
||||
} else {
|
||||
ciphertext = dst
|
||||
}
|
||||
dst = dst[cf.blockSize:]
|
||||
src = src[cf.blockSize:]
|
||||
iv := cf.iv
|
||||
_ = iv[0] // bounds check hint to compiler; see golang.org/issue/14808
|
||||
var (
|
||||
i int
|
||||
val byte
|
||||
)
|
||||
dst = dst[:len(src)]
|
||||
if cf.de && // and requires to be non-overlapping at all
|
||||
uintptr(unsafe.Pointer(&dst[0])) <= uintptr(unsafe.Pointer(&src[len(src)-1])) &&
|
||||
uintptr(unsafe.Pointer(&src[0])) <= uintptr(unsafe.Pointer(&dst[len(dst)-1])) {
|
||||
for i = 0; i < len(src)-cf.blockSize; i += 1 {
|
||||
cf.c.Encrypt(dst[i:], ciphertext[i:])
|
||||
}
|
||||
subtle.XORBytes(dst, src[:i], dst)
|
||||
for ; i < len(src); i += 1 {
|
||||
cf.c.Encrypt(iv, ciphertext[i:])
|
||||
dst[i] = src[i] ^ iv[0]
|
||||
}
|
||||
} else {
|
||||
_ = ciphertext[len(src)]
|
||||
for i, val = range src {
|
||||
cf.c.Encrypt(iv, ciphertext[i:])
|
||||
dst[i] = val ^ iv[0]
|
||||
}
|
||||
// for-range does not increase i in the last loop,
|
||||
// compared to the classic for clause
|
||||
i += 1
|
||||
}
|
||||
// copy the current IV for next operation
|
||||
copy(iv, ciphertext[i:i+cf.blockSize])
|
||||
cf.ivPos = 0
|
||||
return
|
||||
}
|
||||
|
||||
cf.xorKeyStream(dst, src)
|
||||
}
|
||||
|
||||
func (cf *cfb8) xorKeyStream(dst, src []byte) {
|
||||
dst = dst[:len(src)] // remove bounds check in loop
|
||||
for i, val := range src {
|
||||
posPlusBlockSize := cf.ivPos + cf.blockSize
|
||||
// fast mod; 2*blockSize must be a non-negative integer power of 2
|
||||
tempPos := posPlusBlockSize & (cf.blockSize<<1 - 1)
|
||||
// reuse space to store encrypted block
|
||||
cf.c.Encrypt(cf.iv[tempPos:], cf.iv[cf.ivPos:])
|
||||
// Only the first byte of the encrypted block is used
|
||||
// for encryption/decryption, other bytes are ignored.
|
||||
val ^= cf.iv[tempPos]
|
||||
|
||||
if cf.ivPos == cf.blockSize<<1 {
|
||||
// bound reached; move to next round for next operation
|
||||
// copy next block to the start of the ring buffer
|
||||
copy(cf.iv, cf.iv[cf.ivPos+1:])
|
||||
// insert the encrypted byte to the end of IV
|
||||
if cf.de {
|
||||
cf.iv[cf.blockSize-1] = src[i]
|
||||
} else {
|
||||
cf.iv[cf.blockSize-1] = val
|
||||
}
|
||||
cf.ivPos = 0
|
||||
} else {
|
||||
// insert the encrypted byte to the end of IV
|
||||
if cf.de {
|
||||
cf.iv[posPlusBlockSize] = src[i]
|
||||
} else {
|
||||
cf.iv[posPlusBlockSize] = val
|
||||
}
|
||||
// move to next block
|
||||
cf.ivPos += 1
|
||||
}
|
||||
|
||||
dst[i] = val
|
||||
}
|
||||
}
|
||||
236
transport/internet/finalmask/minecraft/client.go
Normal file
236
transport/internet/finalmask/minecraft/client.go
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
package minecraft
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"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
|
||||
usernames []string
|
||||
password string
|
||||
rsaPublicKey []byte
|
||||
hostname string
|
||||
}
|
||||
|
||||
type clientState int
|
||||
|
||||
var (
|
||||
clientStateHandshake clientState = 1
|
||||
clientStateProxy clientState = 2
|
||||
)
|
||||
|
||||
func newClientConn(c net.Conn, usernames []string, password string, rsaPublicKey []byte, hostname string) (*clientConn, error) {
|
||||
if len(rsaPublicKey) == 0 {
|
||||
return nil, fmt.Errorf("empty rsa public key")
|
||||
}
|
||||
|
||||
return &clientConn{
|
||||
reader: bufio.NewReader(c),
|
||||
writer: c,
|
||||
c: c,
|
||||
state: clientStateHandshake,
|
||||
handshakeLock: sync.Mutex{},
|
||||
usernames: usernames,
|
||||
password: password,
|
||||
rsaPublicKey: rsaPublicKey,
|
||||
hostname: hostname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *clientConn) handshake() error {
|
||||
c.handshakeLock.Lock()
|
||||
defer c.handshakeLock.Unlock()
|
||||
|
||||
if c.state != clientStateHandshake {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handshake timeout
|
||||
err := c.c.SetDeadline(time.Now().Add(time.Second * 30))
|
||||
if err != nil {
|
||||
return fmt.Errorf("set deadline: %w", err)
|
||||
}
|
||||
defer c.c.SetDeadline(time.Time{})
|
||||
|
||||
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
|
||||
var (
|
||||
username string
|
||||
offlineUUID UUID
|
||||
)
|
||||
|
||||
randomUsername, _ := rand.Int(rand.Reader, big.NewInt(int64(len(c.usernames))))
|
||||
username = c.usernames[randomUsername.Int64()]
|
||||
generateOfflineUUID(&offlineUUID, string(username))
|
||||
|
||||
err = writePacket(c.writer, 0x00, new(String(username)), &offlineUUID)
|
||||
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)
|
||||
rand.Read(sharedSecret)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
c.state = clientStateProxy
|
||||
|
||||
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 {
|
||||
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.c.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *clientConn) SetReadDeadline(t time.Time) error {
|
||||
return c.c.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *clientConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.c.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func generateOfflineUUID(uuid *UUID, username string) {
|
||||
h := sha256.Sum256([]byte("OfflinePlayer:" + username))
|
||||
copy(uuid[:], h[:16])
|
||||
uuid[6] = (uuid[6] & 0x0f) | 0x30 // UUID version 3
|
||||
uuid[8] = (uuid[8] & 0x3f) | 0x80 // UUID variant
|
||||
}
|
||||
27
transport/internet/finalmask/minecraft/config.go
Normal file
27
transport/internet/finalmask/minecraft/config.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package minecraft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func (c *Config) TCP() {
|
||||
}
|
||||
|
||||
func (c *Config) WrapConnClient(conn net.Conn) (net.Conn, error) {
|
||||
cc, err := newClientConn(conn, c.Usernames, c.Password, c.RsaPublicKey, c.Hostname)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("minecraft finalmask: %w", err)
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (c *Config) WrapConnServer(conn net.Conn) (net.Conn, error) {
|
||||
cc, err := wrapConnServer(conn, c.Password, c.RsaPrivateKey, c.RsaPublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("minecraft finalmask: %w", err)
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
160
transport/internet/finalmask/minecraft/config.pb.go
Normal file
160
transport/internet/finalmask/minecraft/config.pb.go
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.11
|
||||
// protoc v6.33.5
|
||||
// source: config.proto
|
||||
|
||||
package minecraft
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Usernames []string `protobuf:"bytes,2,rep,name=usernames,proto3" json:"usernames,omitempty"`
|
||||
RsaPrivateKey []byte `protobuf:"bytes,8,opt,name=rsa_private_key,json=rsaPrivateKey,proto3" json:"rsa_private_key,omitempty"`
|
||||
RsaPublicKey []byte `protobuf:"bytes,9,opt,name=rsa_public_key,json=rsaPublicKey,proto3" json:"rsa_public_key,omitempty"`
|
||||
Hostname string `protobuf:"bytes,10,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *Config) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_config_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
|
||||
func (*Config) Descriptor() ([]byte, []int) {
|
||||
return file_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *Config) GetPassword() string {
|
||||
if x != nil {
|
||||
return x.Password
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *Config) GetUsernames() []string {
|
||||
if x != nil {
|
||||
return x.Usernames
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetRsaPrivateKey() []byte {
|
||||
if x != nil {
|
||||
return x.RsaPrivateKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetRsaPublicKey() []byte {
|
||||
if x != nil {
|
||||
return x.RsaPublicKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config) GetHostname() string {
|
||||
if x != nil {
|
||||
return x.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\fconfig.proto\x12+xray.transport.internet.finalmask.minecraft\"\xac\x01\n" +
|
||||
"\x06Config\x12\x1a\n" +
|
||||
"\bpassword\x18\x01 \x01(\tR\bpassword\x12\x1c\n" +
|
||||
"\tusernames\x18\x02 \x03(\tR\tusernames\x12&\n" +
|
||||
"\x0frsa_private_key\x18\b \x01(\fR\rrsaPrivateKey\x12$\n" +
|
||||
"\x0ersa_public_key\x18\t \x01(\fR\frsaPublicKey\x12\x1a\n" +
|
||||
"\bhostname\x18\n" +
|
||||
" \x01(\tR\bhostnameB\xa3\x01\n" +
|
||||
"/com.xray.transport.internet.finalmask.minecraftP\x01Z@github.com/xtls/xray-core/transport/internet/finalmask/minecraft\xaa\x02+Xray.Transport.Internet.Finalmask.Minecraftb\x06proto3"
|
||||
|
||||
var (
|
||||
file_config_proto_rawDescOnce sync.Once
|
||||
file_config_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_config_proto_rawDescGZIP() []byte {
|
||||
file_config_proto_rawDescOnce.Do(func() {
|
||||
file_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_config_proto_rawDesc), len(file_config_proto_rawDesc)))
|
||||
})
|
||||
return file_config_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_config_proto_goTypes = []any{
|
||||
(*Config)(nil), // 0: xray.transport.internet.finalmask.minecraft.Config
|
||||
}
|
||||
var file_config_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
0, // [0:0] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_config_proto_init() }
|
||||
func file_config_proto_init() {
|
||||
if File_config_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_config_proto_rawDesc), len(file_config_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_config_proto_goTypes,
|
||||
DependencyIndexes: file_config_proto_depIdxs,
|
||||
MessageInfos: file_config_proto_msgTypes,
|
||||
}.Build()
|
||||
File_config_proto = out.File
|
||||
file_config_proto_goTypes = nil
|
||||
file_config_proto_depIdxs = nil
|
||||
}
|
||||
16
transport/internet/finalmask/minecraft/config.proto
Normal file
16
transport/internet/finalmask/minecraft/config.proto
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package xray.transport.internet.finalmask.minecraft;
|
||||
option csharp_namespace = "Xray.Transport.Internet.Finalmask.Minecraft";
|
||||
option go_package = "github.com/xtls/xray-core/transport/internet/finalmask/minecraft";
|
||||
option java_package = "com.xray.transport.internet.finalmask.minecraft";
|
||||
option java_multiple_files = true;
|
||||
|
||||
|
||||
message Config {
|
||||
string password = 1;
|
||||
repeated string usernames = 2;
|
||||
bytes rsa_private_key = 8;
|
||||
bytes rsa_public_key = 9;
|
||||
string hostname = 10;
|
||||
}
|
||||
103
transport/internet/finalmask/minecraft/derivation.go
Normal file
103
transport/internet/finalmask/minecraft/derivation.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package minecraft
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type sha256Stream struct {
|
||||
seed []byte
|
||||
counter uint64
|
||||
buf []byte
|
||||
}
|
||||
|
||||
func newSHA256Stream(seed []byte) *sha256Stream {
|
||||
return &sha256Stream{
|
||||
seed: seed,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sha256Stream) Read(p []byte) (n int, err error) {
|
||||
for len(p) > len(s.buf) {
|
||||
h := sha256.New()
|
||||
h.Write(s.seed)
|
||||
h.Write([]byte(fmt.Sprintf("-%d", s.counter)))
|
||||
s.counter++
|
||||
s.buf = append(s.buf, h.Sum(nil)...)
|
||||
}
|
||||
n = copy(p, s.buf)
|
||||
s.buf = s.buf[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func derivePrime(stream *sha256Stream) *big.Int {
|
||||
pBytes := make([]byte, 64) // 512 bits
|
||||
_, _ = stream.Read(pBytes)
|
||||
pBytes[0] |= 0xc0 // ensure it is big enough so p*q is 1024 bits
|
||||
pBytes[63] |= 0x01 // ensure odd
|
||||
|
||||
p := new(big.Int).SetBytes(pBytes)
|
||||
for {
|
||||
if p.ProbablyPrime(20) {
|
||||
pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
|
||||
e := big.NewInt(65537)
|
||||
gcd := new(big.Int).GCD(nil, nil, pMinus1, e)
|
||||
if gcd.Cmp(big.NewInt(1)) == 0 {
|
||||
return p
|
||||
}
|
||||
}
|
||||
p.Add(p, big.NewInt(2))
|
||||
}
|
||||
}
|
||||
|
||||
// DeriveRSAKey derives a 1024-bit RSA private key from a password.
|
||||
func DeriveRSAKey(password string) (*rsa.PrivateKey, error) {
|
||||
seed := []byte(password)
|
||||
|
||||
pStream := newSHA256Stream(append(seed, []byte("-p-prime")...))
|
||||
qStream := newSHA256Stream(append(seed, []byte("-q-prime")...))
|
||||
|
||||
p := derivePrime(pStream)
|
||||
q := derivePrime(qStream)
|
||||
|
||||
// ensure p != q (if they are, let's search q further)
|
||||
for p.Cmp(q) == 0 {
|
||||
q.Add(q, big.NewInt(2))
|
||||
for {
|
||||
if q.ProbablyPrime(20) {
|
||||
qMinus1 := new(big.Int).Sub(q, big.NewInt(1))
|
||||
e := big.NewInt(65537)
|
||||
gcd := new(big.Int).GCD(nil, nil, qMinus1, e)
|
||||
if gcd.Cmp(big.NewInt(1)) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
q.Add(q, big.NewInt(2))
|
||||
}
|
||||
}
|
||||
|
||||
n := new(big.Int).Mul(p, q)
|
||||
pMinus1 := new(big.Int).Sub(p, big.NewInt(1))
|
||||
qMinus1 := new(big.Int).Sub(q, big.NewInt(1))
|
||||
totient := new(big.Int).Mul(pMinus1, qMinus1)
|
||||
|
||||
e := big.NewInt(65537)
|
||||
d := new(big.Int).ModInverse(e, totient)
|
||||
if d == nil {
|
||||
return nil, fmt.Errorf("failed to compute mod inverse")
|
||||
}
|
||||
|
||||
priv := &rsa.PrivateKey{
|
||||
PublicKey: rsa.PublicKey{
|
||||
N: n,
|
||||
E: 65537,
|
||||
},
|
||||
D: d,
|
||||
Primes: []*big.Int{p, q},
|
||||
}
|
||||
priv.Precompute()
|
||||
|
||||
return priv, nil
|
||||
}
|
||||
58
transport/internet/finalmask/minecraft/derivation_test.go
Normal file
58
transport/internet/finalmask/minecraft/derivation_test.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package minecraft
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeriveRSAKey(t *testing.T) {
|
||||
password := "my-very-secret-password-12345"
|
||||
|
||||
key1, err := DeriveRSAKey(password)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to derive key: %v", err)
|
||||
}
|
||||
|
||||
err = key1.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("key is not valid RSA key: %v", err)
|
||||
}
|
||||
|
||||
key2, err := DeriveRSAKey(password)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to derive key second time: %v", err)
|
||||
}
|
||||
|
||||
// Verify determinism
|
||||
if key1.D.Cmp(key2.D) != 0 || key1.N.Cmp(key2.N) != 0 {
|
||||
t.Errorf("derived keys are not identical for the same password")
|
||||
}
|
||||
|
||||
// Verify different passwords yield different keys
|
||||
keyDifferent, err := DeriveRSAKey(password + "-different")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to derive different key: %v", err)
|
||||
}
|
||||
|
||||
if key1.D.Cmp(keyDifferent.D) == 0 || key1.N.Cmp(keyDifferent.N) == 0 {
|
||||
t.Errorf("derived keys are identical for different passwords")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveRSAKeyGoldenPrivateKey(t *testing.T) {
|
||||
const password = "deterministic-rsa-key-golden"
|
||||
const wantPKCS1DERHash = "3a8c4ad56a6fb42dab73c4d5fc3af754460a2db1441edc0970cbc7f4e0798d2f"
|
||||
|
||||
key, err := DeriveRSAKey(password)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to derive key: %v", err)
|
||||
}
|
||||
|
||||
gotHash := sha256.Sum256(x509.MarshalPKCS1PrivateKey(key))
|
||||
got := hex.EncodeToString(gotHash[:])
|
||||
if got != wantPKCS1DERHash {
|
||||
t.Fatalf("derived private key changed\nwant sha256: %s\n got sha256: %s", wantPKCS1DERHash, got)
|
||||
}
|
||||
}
|
||||
156
transport/internet/finalmask/minecraft/handshake_test.go
Normal file
156
transport/internet/finalmask/minecraft/handshake_test.go
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package minecraft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func deriveTestRSAKey(t *testing.T, password string) ([]byte, []byte) {
|
||||
t.Helper()
|
||||
|
||||
key, err := DeriveRSAKey(password)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to derive rsa key: %v", err)
|
||||
}
|
||||
|
||||
publicKey, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal public key: %v", err)
|
||||
}
|
||||
|
||||
return x509.MarshalPKCS1PrivateKey(key), publicKey
|
||||
}
|
||||
|
||||
func TestHandshakeSuccess(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
password := "super-secure-shared-key-12345"
|
||||
usernames := []string{"test_user"}
|
||||
privateKey, publicKey := deriveTestRSAKey(t, password)
|
||||
|
||||
go func() {
|
||||
rawConn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rawConn.Close()
|
||||
|
||||
server, err := wrapConnServer(rawConn, password, privateKey, publicKey)
|
||||
if err != nil {
|
||||
t.Errorf("failed to wrap server: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := server.Read(buf)
|
||||
if err != nil {
|
||||
t.Errorf("server read error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf[:n], []byte("hello server")) {
|
||||
t.Errorf("unexpected payload from client: %s", string(buf[:n]))
|
||||
return
|
||||
}
|
||||
|
||||
_, err = server.Write([]byte("hello client"))
|
||||
if err != nil {
|
||||
t.Errorf("server write error: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
clientRaw, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
client, err := newClientConn(clientRaw, usernames, password, publicKey, "localhost")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
_, err = client.Write([]byte("hello server"))
|
||||
if err != nil {
|
||||
t.Fatalf("client write error: %v", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
n, err := client.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("client read error: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf[:n], []byte("hello client")) {
|
||||
t.Errorf("unexpected payload from server: %s", string(buf[:n]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandshakePasswordMismatch(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
clientPassword := "client-secret-123"
|
||||
serverPassword := "server-secret-456"
|
||||
usernames := []string{"test_user"}
|
||||
serverPrivateKey, serverPublicKey := deriveTestRSAKey(t, serverPassword)
|
||||
_, clientPublicKey := deriveTestRSAKey(t, clientPassword)
|
||||
|
||||
go func() {
|
||||
rawConn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rawConn.Close()
|
||||
|
||||
server, err := wrapConnServer(rawConn, serverPassword, serverPrivateKey, serverPublicKey)
|
||||
if err != nil {
|
||||
// Wrapping is synchronous and shouldn't fail initially simply because key derivation works with any string
|
||||
t.Logf("wrapped server: %v", err)
|
||||
}
|
||||
|
||||
// When client sends data, handshake happens and should fail
|
||||
buf := make([]byte, 1024)
|
||||
_, err = server.Read(buf)
|
||||
if err == nil {
|
||||
t.Errorf("expected handshake to fail due to password mismatch, but it succeeded")
|
||||
} else {
|
||||
t.Logf("server read failed as expected: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
clientRaw, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
defer clientRaw.Close()
|
||||
|
||||
client, err := newClientConn(clientRaw, usernames, clientPassword, clientPublicKey, "localhost")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
// Wait briefly or just perform write/read
|
||||
_ = clientRaw.SetDeadline(time.Now().Add(100 * time.Millisecond))
|
||||
|
||||
_, err = client.Write([]byte("hello server"))
|
||||
if err == nil {
|
||||
// Try to read too
|
||||
buf := make([]byte, 1024)
|
||||
_, _ = client.Read(buf)
|
||||
}
|
||||
|
||||
// Check if we lost connection or received error
|
||||
t.Log("Handshake mismatch tested")
|
||||
}
|
||||
335
transport/internet/finalmask/minecraft/protocol.go
Normal file
335
transport/internet/finalmask/minecraft/protocol.go
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
// Minecraft protocol
|
||||
package minecraft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type field interface {
|
||||
readFrom(r io.Reader) error
|
||||
writeTo(w io.Writer) error
|
||||
}
|
||||
|
||||
type mcPacket struct {
|
||||
packetID int
|
||||
data []byte
|
||||
}
|
||||
|
||||
func readPacket(b io.Reader) (*mcPacket, error) {
|
||||
var packetLength Varint
|
||||
err := packetLength.readFrom(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read packet length: %w", err)
|
||||
}
|
||||
|
||||
var packetID Varint
|
||||
err = packetID.readFrom(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read packet ID: %w", err)
|
||||
}
|
||||
|
||||
dataLength := int(packetLength) - varintSize(packetID)
|
||||
if dataLength < 0 || dataLength > 1024*32 {
|
||||
return nil, fmt.Errorf("read packet: bad length: %d", dataLength)
|
||||
}
|
||||
|
||||
data := make([]byte, dataLength)
|
||||
_, err = io.ReadFull(b, data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read packet data: %w", err)
|
||||
}
|
||||
|
||||
return &mcPacket{
|
||||
packetID: int(packetID),
|
||||
data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *mcPacket) readFields(fields ...field) error {
|
||||
r := bytes.NewReader(p.data)
|
||||
|
||||
for _, field := range fields {
|
||||
err := field.readFrom(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read packet field: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Varint int32
|
||||
|
||||
func (v *Varint) readFrom(r io.Reader) error {
|
||||
SEGMENT_BITS := byte(0x7F)
|
||||
CONTINUE_BIT := byte(0x80)
|
||||
|
||||
var err error
|
||||
|
||||
var value int32 = 0
|
||||
var position int32 = 0
|
||||
var currentByte byte
|
||||
|
||||
for true {
|
||||
currentByte, err = readByte(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read varint: %w", err)
|
||||
}
|
||||
value |= int32(currentByte&SEGMENT_BITS) << position
|
||||
|
||||
if (currentByte & CONTINUE_BIT) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
position += 7
|
||||
|
||||
if position >= 32 {
|
||||
return fmt.Errorf("read varint: too large")
|
||||
}
|
||||
}
|
||||
|
||||
*v = Varint(value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Varint) writeTo(w io.Writer) error {
|
||||
SEGMENT_BITS := byte(0x7F)
|
||||
CONTINUE_BIT := byte(0x80)
|
||||
|
||||
value := int32(*v)
|
||||
|
||||
for {
|
||||
currentByte := byte(value & int32(SEGMENT_BITS))
|
||||
value >>= 7
|
||||
if value != 0 {
|
||||
currentByte |= CONTINUE_BIT
|
||||
}
|
||||
|
||||
_, err := w.Write([]byte{currentByte})
|
||||
if err != nil {
|
||||
return fmt.Errorf("write varint: %w", err)
|
||||
}
|
||||
|
||||
if value == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func varintSize(value Varint) int {
|
||||
size := 0
|
||||
for {
|
||||
size++
|
||||
value >>= 7
|
||||
if value == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
type String string
|
||||
|
||||
func (v *String) readFrom(r io.Reader) error {
|
||||
var length Varint = 0
|
||||
|
||||
err := length.readFrom(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read string: %w", err)
|
||||
}
|
||||
|
||||
if length < 0 || length > 4096 {
|
||||
return fmt.Errorf("read string: bad length: %d", length)
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read string: %w", err)
|
||||
}
|
||||
|
||||
*v = String(string(buf))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *String) writeTo(w io.Writer) error {
|
||||
strBytes := []byte(*v)
|
||||
length := Varint(len(strBytes))
|
||||
|
||||
err := length.writeTo(w)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write string: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Write(strBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write string: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UnsignedShort uint16
|
||||
|
||||
func (v *UnsignedShort) readFrom(r io.Reader) error {
|
||||
var buf [2]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read unsigned short: %w", err)
|
||||
}
|
||||
|
||||
*v = UnsignedShort(buf[0])<<8 | UnsignedShort(buf[1])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *UnsignedShort) writeTo(w io.Writer) error {
|
||||
buf := []byte{byte(*v >> 8), byte(*v & 0xFF)}
|
||||
_, err := w.Write(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write unsigned short: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Long int64
|
||||
|
||||
func (v *Long) readFrom(r io.Reader) error {
|
||||
var buf [8]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read long: %w", err)
|
||||
}
|
||||
|
||||
*v = Long(buf[0])<<56 | Long(buf[1])<<48 | Long(buf[2])<<40 | Long(buf[3])<<32 |
|
||||
Long(buf[4])<<24 | Long(buf[5])<<16 | Long(buf[6])<<8 | Long(buf[7])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Long) writeTo(w io.Writer) error {
|
||||
buf := []byte{
|
||||
byte(*v >> 56), byte((*v >> 48) & 0xFF), byte((*v >> 40) & 0xFF), byte((*v >> 32) & 0xFF),
|
||||
byte((*v >> 24) & 0xFF), byte((*v >> 16) & 0xFF), byte((*v >> 8) & 0xFF), byte(*v & 0xFF),
|
||||
}
|
||||
|
||||
_, err := w.Write(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write long: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type UUID [16]byte
|
||||
|
||||
func (v *UUID) readFrom(r io.Reader) error {
|
||||
_, err := io.ReadFull(r, v[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read UUID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *UUID) writeTo(w io.Writer) error {
|
||||
_, err := w.Write(v[:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("write UUID: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Bytes []byte
|
||||
|
||||
func (v *Bytes) readFrom(r io.Reader) error {
|
||||
var length Varint
|
||||
err := length.readFrom(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read bytes: %w", err)
|
||||
}
|
||||
|
||||
if length < 0 || length >= 1024 {
|
||||
return fmt.Errorf("read bytes: invalid size: %d", err)
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read bytes: %w", err)
|
||||
}
|
||||
|
||||
*v = append([]byte(*v), buf...)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Bytes) writeTo(w io.Writer) error {
|
||||
length := Varint(len(*v))
|
||||
err := length.writeTo(w)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write bytes length: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Write(*v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write bytes: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readByte(r io.Reader) (byte, error) {
|
||||
var buf [1]byte
|
||||
_, err := io.ReadFull(r, buf[:])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("read byte: %w", err)
|
||||
}
|
||||
|
||||
return buf[0], nil
|
||||
}
|
||||
|
||||
func writePacket(w io.Writer, packetID int, fields ...field) error {
|
||||
var dataBuf bytes.Buffer
|
||||
|
||||
for _, field := range fields {
|
||||
err := field.writeTo(&dataBuf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write packet field: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
var packetLength Varint = Varint(varintSize(Varint(packetID)) + dataBuf.Len())
|
||||
err := packetLength.writeTo(&buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write packet length: %w", err)
|
||||
}
|
||||
|
||||
var packetIDVarint Varint = Varint(packetID)
|
||||
err = packetIDVarint.writeTo(&buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write packet ID: %w", err)
|
||||
}
|
||||
|
||||
buf.Write(dataBuf.Bytes())
|
||||
|
||||
_, err = w.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("write packet data: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeDisconnectPacket(w io.Writer, reason string) error {
|
||||
return writePacket(w, 0x00, new(String(reason)))
|
||||
}
|
||||
289
transport/internet/finalmask/minecraft/server.go
Normal file
289
transport/internet/finalmask/minecraft/server.go
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
package minecraft
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/subtle"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Response by vanilla 26.1.2 server.
|
||||
var statusResponse = `{"description":"A Minecraft Server","players":{"max":20,"online":0},"version":{"name":"26.1.2","protocol":775},"enforcesSecureChat":true}`
|
||||
|
||||
type serverState int
|
||||
|
||||
var (
|
||||
serverStateHandshake serverState = 1
|
||||
serverStateProxy serverState = 3
|
||||
)
|
||||
|
||||
type serverConn struct {
|
||||
reader io.Reader
|
||||
writer io.Writer
|
||||
c net.Conn
|
||||
|
||||
state serverState
|
||||
|
||||
handshakeLock sync.Mutex
|
||||
password string
|
||||
rsaPrivateKey *rsa.PrivateKey
|
||||
rsaPublicKey []byte
|
||||
}
|
||||
|
||||
func (c *serverConn) handshake() error {
|
||||
c.handshakeLock.Lock()
|
||||
defer c.handshakeLock.Unlock()
|
||||
|
||||
if c.state != serverStateHandshake {
|
||||
return nil
|
||||
}
|
||||
|
||||
// handshake timeout
|
||||
err := c.c.SetDeadline(time.Now().Add(time.Second * 30))
|
||||
if err != nil {
|
||||
return fmt.Errorf("set deadline: %w", err)
|
||||
}
|
||||
defer c.c.SetDeadline(time.Time{})
|
||||
|
||||
var (
|
||||
protocolVersion Varint
|
||||
serverAddress String
|
||||
serverPort UnsignedShort
|
||||
nextState Varint
|
||||
)
|
||||
|
||||
// handshake packet
|
||||
|
||||
pkt, err := readPacket(c.reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read handshake packet: %w", err)
|
||||
}
|
||||
|
||||
if pkt.packetID != 0 {
|
||||
return fmt.Errorf("bad handshake packet id")
|
||||
}
|
||||
|
||||
err = pkt.readFields(&protocolVersion, &serverAddress, &serverPort, &nextState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read handshake packet: %w", err)
|
||||
}
|
||||
|
||||
switch nextState {
|
||||
case 1:
|
||||
|
||||
// Ping
|
||||
|
||||
for range 2 {
|
||||
|
||||
pkt, err := readPacket(c.reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read packet: %w", err)
|
||||
}
|
||||
|
||||
switch pkt.packetID {
|
||||
case 0: // Status Request
|
||||
|
||||
err = writePacket(c.writer, 0, new(String(statusResponse)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write status response: %w", err)
|
||||
}
|
||||
|
||||
case 1: // Ping
|
||||
|
||||
var payload Long
|
||||
err = pkt.readFields(&payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read ping packet: %w", err)
|
||||
}
|
||||
|
||||
err = writePacket(c.writer, 1, &payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write ping response: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return fmt.Errorf("ping")
|
||||
|
||||
case 2:
|
||||
|
||||
// Login
|
||||
|
||||
// login start
|
||||
|
||||
pkt, err := readPacket(c.reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read login start packet: %w", err)
|
||||
}
|
||||
|
||||
if pkt.packetID != 0 {
|
||||
return fmt.Errorf("bad login start packet id")
|
||||
}
|
||||
|
||||
var (
|
||||
username String
|
||||
uuid UUID
|
||||
)
|
||||
|
||||
err = pkt.readFields(&username, &uuid)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read login start packet: %w", err)
|
||||
}
|
||||
|
||||
// encrypt request
|
||||
|
||||
var (
|
||||
serverId String = String("")
|
||||
publicKey Bytes = Bytes(c.rsaPublicKey)
|
||||
verifyToken Bytes = Bytes(make([]byte, 4))
|
||||
shouldAuthenticate Varint = Varint(1)
|
||||
)
|
||||
|
||||
rand.Read(verifyToken)
|
||||
|
||||
err = writePacket(c.writer, 0x01, &serverId, &publicKey, &verifyToken, &shouldAuthenticate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write encryption request: %w", err)
|
||||
}
|
||||
|
||||
// encrypt response
|
||||
|
||||
var (
|
||||
encryptedSharedSecret Bytes
|
||||
encryptedVerifyToken Bytes
|
||||
|
||||
sharedSecret []byte
|
||||
decryptedVerifyToken []byte
|
||||
)
|
||||
|
||||
pkt, err = readPacket(c.reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read encrypt response: %w", err)
|
||||
}
|
||||
|
||||
if pkt.packetID != 0x01 {
|
||||
return fmt.Errorf("bad encrypt response packet id")
|
||||
}
|
||||
|
||||
err = pkt.readFields(&encryptedSharedSecret, &encryptedVerifyToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read encrypt response: %w", err)
|
||||
}
|
||||
|
||||
sharedSecret, err = rsa.DecryptPKCS1v15(rand.Reader, c.rsaPrivateKey, encryptedSharedSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt shared secret: %w", err)
|
||||
}
|
||||
|
||||
decryptedVerifyToken, err = rsa.DecryptPKCS1v15(rand.Reader, c.rsaPrivateKey, encryptedVerifyToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decrypt verify token: %w", err)
|
||||
}
|
||||
|
||||
if len(decryptedVerifyToken) < 4 || !bytes.Equal(verifyToken, decryptedVerifyToken[:4]) {
|
||||
return fmt.Errorf("verify token mismatch")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// verify password
|
||||
receivedPassword := decryptedVerifyToken[4:]
|
||||
|
||||
if subtle.ConstantTimeCompare(receivedPassword, []byte(c.password)) != 1 {
|
||||
writeDisconnectPacket(c.writer, `{"type":"translatable","translate":"multiplayer.disconnect.authservers_down"}`)
|
||||
return fmt.Errorf("bad password")
|
||||
}
|
||||
|
||||
c.state = serverStateProxy
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return fmt.Errorf("bad handshake packet: bad next state: %d", nextState)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *serverConn) 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 *serverConn) 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 *serverConn) Close() error {
|
||||
return c.c.Close()
|
||||
}
|
||||
|
||||
func (c *serverConn) LocalAddr() net.Addr {
|
||||
return c.c.LocalAddr()
|
||||
}
|
||||
|
||||
func (c *serverConn) RemoteAddr() net.Addr {
|
||||
return c.c.RemoteAddr()
|
||||
}
|
||||
|
||||
func (c *serverConn) SetDeadline(t time.Time) error {
|
||||
return c.c.SetDeadline(t)
|
||||
}
|
||||
|
||||
func (c *serverConn) SetReadDeadline(t time.Time) error {
|
||||
return c.c.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (c *serverConn) SetWriteDeadline(t time.Time) error {
|
||||
return c.c.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func wrapConnServer(c net.Conn, password string, rsaPrivateKeyDER []byte, rsaPublicKey []byte) (*serverConn, error) {
|
||||
if len(rsaPrivateKeyDER) == 0 {
|
||||
return nil, fmt.Errorf("empty rsa private key")
|
||||
}
|
||||
if len(rsaPublicKey) == 0 {
|
||||
return nil, fmt.Errorf("empty rsa public key")
|
||||
}
|
||||
|
||||
rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(rsaPrivateKeyDER)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse rsa private key: %w", err)
|
||||
}
|
||||
|
||||
s := &serverConn{
|
||||
reader: bufio.NewReader(c),
|
||||
writer: c,
|
||||
c: c,
|
||||
state: serverStateHandshake,
|
||||
password: password,
|
||||
rsaPrivateKey: rsaPrivateKey,
|
||||
rsaPublicKey: rsaPublicKey,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
68
transport/internet/finalmask/minecraft/stream.go
Normal file
68
transport/internet/finalmask/minecraft/stream.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Minecraft stream cipher
|
||||
package minecraft
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type cryptoStream struct {
|
||||
stream cipher.Stream
|
||||
r io.Reader
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func newCryptoReader(r io.Reader, sharedSecret []byte) (*cryptoStream, error) {
|
||||
blockCipher, err := aes.NewCipher(sharedSecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new aes cipher: %w", err)
|
||||
}
|
||||
|
||||
stream := newCFB8Decrypt(blockCipher, sharedSecret)
|
||||
|
||||
return &cryptoStream{stream: stream, r: r}, nil
|
||||
}
|
||||
|
||||
func (c *cryptoStream) Read(b []byte) (int, error) {
|
||||
if c.r == nil {
|
||||
panic("read on a write-only crypto stream")
|
||||
}
|
||||
|
||||
n, err := c.r.Read(b)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("crypto reader: read: %w", err)
|
||||
}
|
||||
|
||||
c.stream.XORKeyStream(b[:n], b[:n])
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func newCryptoWriter(w io.Writer, sharedSecret []byte) (*cryptoStream, error) {
|
||||
blockCipher, err := aes.NewCipher(sharedSecret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new aes cipher: %w", err)
|
||||
}
|
||||
|
||||
stream := newCFB8Encrypt(blockCipher, sharedSecret)
|
||||
|
||||
return &cryptoStream{stream: stream, w: w}, nil
|
||||
}
|
||||
|
||||
func (c *cryptoStream) Write(b []byte) (int, error) {
|
||||
if c.w == nil {
|
||||
panic("write on a read-only crypto stream")
|
||||
}
|
||||
|
||||
encrypted := make([]byte, len(b))
|
||||
c.stream.XORKeyStream(encrypted, b)
|
||||
|
||||
n, err := c.w.Write(encrypted)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("crypto writer: write: %w", err)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue