diff --git a/cmd/substitute_server/substitute.go b/cmd/substitute_server/substitute.go deleted file mode 100644 index ee2eeb9..0000000 --- a/cmd/substitute_server/substitute.go +++ /dev/null @@ -1,459 +0,0 @@ -//创建一个服务器代理,自带正版验证登录 -//Create a server proxy with authenticated login -//非正版客户端连接此程序时可以以正版身份进入指定服务器 -//Offline-clients can access the designated server as online when connecting to this program - -//That is, it implements a simple server and client -package main - -import ( - "bytes" - "crypto/aes" - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/x509" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "strings" - - "github.com/Tnze/go-mc/yggdrasil" - "github.com/Tnze/go-mc/bot" - "github.com/Tnze/go-mc/chat" - _ "github.com/Tnze/go-mc/data/lang/en-us" - "github.com/Tnze/go-mc/net" - "github.com/Tnze/go-mc/net/CFB8" - pk "github.com/Tnze/go-mc/net/packet" - "github.com/google/uuid" -) - -const verifyTokenLen = 16 - -//Proxy login info -const ( - Email = "YourEmail" - Paswd = "YourPassword" -) - -//Proxy server settings -const ( - ServerIP = "localhost" - ServerPort = 25565 - onlineMode = true -) - -//Target server address -const ( - RemoteServerIP = "YourTargetServerIP" - RemoteServerPort = 25565 -) - -func main() { - listener, err := net.ListenMC(fmt.Sprintf("%s:%d", ServerIP, ServerPort)) - if err != nil { - panic(err) - } - for { - conn, err := listener.Accept() - if err != nil { - panic(err) - } - go Handle(conn) - } -} - -//Threshold 指定了数据传输时最小压缩包大小 -const Threshold = 256 - -//Client 封装了与客户端之间的底层的网络交互 -type Client struct { - net.Conn - - ProtocolVersion int32 - Name string - ID uuid.UUID - skin string -} - -//OnPlayer 在正版玩家连接时被调用,其返回值将被作为断开连接的原因被发送给客户端 -//返回值应当为一个JSON Chat值,例如`"msg"` -var OnPlayer func(name string, UUID uuid.UUID, protocol int32) string - -//Handle 接受客户端连接 -func Handle(conn net.Conn) { - defer conn.Close() - c := Client{Conn: conn} - - nextState, err := c.handshake() - if err != nil { - // log.Println(err) - return - } - - const ( - CheckState = 1 - PlayerLogin = 2 - ) - switch nextState { - case CheckState: - c.status() - case PlayerLogin: - signal := make(chan int) - client := bot.NewClient() - auth, err := yggdrasil.Authenticate(Email, Paswd) - if err != nil { - panic(err) - } - client.Name, client.Auth.UUID, client.AsTk = auth.SelectedProfile.Name, auth.SelectedProfile.ID, auth.AccessToken - go func() { - err := client.JoinServer("localhost", 25565) - if err != nil { - log.Fatal(err) - } - signal <- 1 - }() - - log.Println(c.Conn.Socket.RemoteAddr(), "协议号", c.ProtocolVersion) - - err = c.login() - if err != nil { - msg := chat.Message{Translate: ("multiplayer.disconnect." + err.Error())} - jmsg, err := json.Marshal(msg) - if err != nil { - return - } - packet := pk.Packet{ID: 0x00, Data: pk.String(string(jmsg)).Encode()} - c.WritePacket(packet) - return - } - <-signal - fmt.Println("start proxy") - conn := client.Conn() - go func() { - for { - p, err := c.ReadPacket() - if err != nil { - fmt.Println(err) - return - } - if err := conn.WritePacket(p); err != nil { - fmt.Println(err) - return - } - } - }() - for { - p, err := conn.ReadPacket() - if err != nil { - fmt.Println(err) - return - } - if err := c.WritePacket(p); err != nil { - fmt.Println(err) - return - } - } - } -} - -func (c *Client) handshake() (nextState int32, err error) { - p, err := c.ReadPacket() - if err != nil { - return -1, err - } - if p.ID != 0 { - return -1, fmt.Errorf("packet ID 0x%X is not handshake", p.ID) - } - - var ( - sid pk.String - spt pk.Short - ) - if err := p.Scan( - (*pk.VarInt)(&c.ProtocolVersion), - &sid, &spt, - (*pk.VarInt)(&nextState)); err != nil { - return -1, err - } - - //检查服务器ID和端口是否匹配 - // if sid != ServerID || uint16(spt) != ServerPort { - // return -1, fmt.Errorf("server address rejected") - // } - - return nextState, nil -} - -func (c *Client) status() { - for i := 0; i < 2; i++ { - p, err := c.ReadPacket() - if err != nil { - break - } - - switch p.ID { - case 0x00: - respPack := getStatus() - c.WritePacket(respPack) - case 0x01: - c.WritePacket(p) - } - } -} - -func getStatus() pk.Packet { - return pk.Packet{ - ID: 0x00, - Data: pk.String(` - { - "version": { - "name": "1.14.1", - "protocol": 480 - }, - "players": { - "max": 1, - "online": 0, - "sample": [] - }, - "description": { - "text": "军刀破服" - } - } - `).Encode(), - } -} - -func disconnectID(protocol int32) byte { - switch protocol { - case 404: - return 0x1B - case 477, 480: - return 0x1A - default: - return 0x1A - } -} - -func (c *Client) login() (err error) { - c.Name, err = c.loginStart() - if err != nil { - return fmt.Errorf("unexpected_query_response") - } - - if Threshold >= 0 { - err = c.setCompression(Threshold) - if err != nil { - return fmt.Errorf("unexpected_query_response") - } - } - - if onlineMode { - key, err := rsa.GenerateKey(rand.Reader, 1024) - if err != nil { - return fmt.Errorf("unexpected_query_response") - } - - publicKey, err := x509.MarshalPKIXPublicKey(&key.PublicKey) - if err != nil { - return fmt.Errorf("generic") - } - - VT1, err := c.encryptionRequest(publicKey) - if err != nil { - return fmt.Errorf("generic") - } - - ESharedSecret, EVerifyToken, err := c.encryptionResponse() - if err != nil { - return fmt.Errorf("generic") - } - - SharedSecret, err := rsa.DecryptPKCS1v15(rand.Reader, key, ESharedSecret) - if err != nil { - return fmt.Errorf("generic") - } - VT2, err := rsa.DecryptPKCS1v15(rand.Reader, key, EVerifyToken) - if err != nil { - return fmt.Errorf("generic") - } - - if !bytes.Equal(VT1, VT2) { - return fmt.Errorf("generic") - } - - b, err := aes.NewCipher(SharedSecret) - if err != nil { - return fmt.Errorf("generic") - } - //启用加密 - c.SetCipher( - CFB8.NewCFB8Encrypt(b, SharedSecret), - CFB8.NewCFB8Decrypt(b, SharedSecret), - ) - hash := authDigest("", SharedSecret, publicKey) - resp, err := c.authentication(hash) - if err != nil { - return fmt.Errorf("authservers_down") - } - - c.ID, err = uuid.Parse(resp.ID) - if err != nil { - return fmt.Errorf("authservers_down") - } - - if c.Name != resp.Name { - return fmt.Errorf("unverified_username") - } - - c.skin = resp.Properties[0].Value - - } - - err = c.loginSuccess() - if err != nil { - return fmt.Errorf("generic") - } - return -} - -func (c *Client) loginStart() (string, error) { - loginStart, err := c.ReadPacket() - if err != nil { - return "", err - } - if loginStart.ID != 0x00 { - return "", fmt.Errorf("0x%02X is not LoginStart packet's ID", loginStart.ID) - } - var name pk.String - err = loginStart.Scan(&name) - return string(name), err -} - -func (c *Client) setCompression(threshold int) error { - sc := pk.Packet{ - ID: 0x03, - Data: pk.VarInt(threshold).Encode(), - } - err := c.WritePacket(sc) - c.SetThreshold(threshold) - return err -} - -func (c *Client) loginSuccess() error { - ls := pk.Packet{ID: 0x02} - ls.Data = append(ls.Data, pk.String(c.ID.String()).Encode()...) - ls.Data = append(ls.Data, pk.String(c.Name).Encode()...) - err := c.WritePacket(ls) - return err -} - -func (c *Client) encryptionRequest(publicKey []byte) ([]byte, error) { - var verifyToken [verifyTokenLen]byte - _, err := rand.Read(verifyToken[:]) - if err != nil { - return nil, err - } - - er := pk.Packet{ID: 0x01} - er.Data = append(er.Data, pk.String("").Encode()...) - er.Data = append(er.Data, pk.VarInt(len(publicKey)).Encode()...) - er.Data = append(er.Data, publicKey...) - er.Data = append(er.Data, pk.VarInt(verifyTokenLen).Encode()...) - er.Data = append(er.Data, verifyToken[:]...) - - err = c.WritePacket(er) - return verifyToken[:], err -} - -func (c *Client) encryptionResponse() ([]byte, []byte, error) { - p, err := c.ReadPacket() - if err != nil { - return nil, nil, err - } - if p.ID != 0x01 { - return nil, nil, fmt.Errorf("0x%02X is not Encryption authResp", p.ID) - } - - var ( - SharedSecret pk.ByteArray - VerifyToken pk.ByteArray - ) - if err := p.Scan(&SharedSecret, &VerifyToken); err != nil { - return nil, nil, err - } - return SharedSecret, VerifyToken, nil -} - -type authResp struct { - ID, Name string - Properties [1]struct { - Name, Value, Signature string - } -} - -func (c *Client) authentication(hash string) (*authResp, error) { - resp, err := http.Get(fmt.Sprintf("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s", - c.Name, hash)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - var Resp authResp - - err = json.Unmarshal(body, &Resp) - if err != nil { - return nil, err - } - - return &Resp, nil -} - -// authDigest computes a special SHA-1 digest required for Minecraft web -// authentication on Premium servers (online-mode=true). -// Source: http://wiki.vg/Protocol_Encryption#Server -// -// Also many, many thanks to SirCmpwn and his wonderful gist (C#): -// https://gist.github.com/SirCmpwn/404223052379e82f91e6 -func authDigest(serverID string, sharedSecret, publicKey []byte) string { - h := sha1.New() - h.Write([]byte(serverID)) - h.Write(sharedSecret) - h.Write(publicKey) - hash := h.Sum(nil) - - // Check for negative hashes - negative := (hash[0] & 0x80) == 0x80 - if negative { - hash = twosComplement(hash) - } - - // Trim away zeroes - res := strings.TrimLeft(fmt.Sprintf("%x", hash), "0") - if negative { - res = "-" + res - } - - return res -} - -// little endian -func twosComplement(p []byte) []byte { - carry := true - for i := len(p) - 1; i >= 0; i-- { - p[i] = byte(^p[i]) - if carry { - carry = p[i] == 0xff - p[i]++ - } - } - return p -} diff --git a/yggdrasil/authenticate.go b/yggdrasil/authenticate.go index 8d76d27..ac0a11c 100644 --- a/yggdrasil/authenticate.go +++ b/yggdrasil/authenticate.go @@ -16,6 +16,11 @@ type agent struct { Version int `json:"version"` } +var defaultAgent = agent{ + Name: "Minecraft", + Version: 1, +} + // authPayload is a yggdrasil request struct type authPayload struct { Agent agent `json:"agent"` @@ -57,10 +62,7 @@ type authResp struct { func Authenticate(user, password string) (*Access, error) { // Payload pl := authPayload{ - Agent: agent{ - Name: "Minecraft", - Version: 1, - }, + Agent: defaultAgent, UserName: user, Password: password, ClientToken: uuid.New().String(), @@ -88,6 +90,6 @@ func (a *Access) SelectedProfile() (ID uuid.UUID, Name string) { return a.ar.SelectedProfile.ID, a.ar.SelectedProfile.Name } -func (a *Access) AccessToken()string{ +func (a *Access) AccessToken() string { return a.ar.AccessToken -} \ No newline at end of file +}