diff --git a/bot/basic/events.go b/bot/basic/events.go index 4e68d43..6e7854e 100644 --- a/bot/basic/events.go +++ b/bot/basic/events.go @@ -20,7 +20,7 @@ type EventsListener struct { func (e EventsListener) Attach(c *bot.Client) { c.Events.AddListener( bot.PacketHandler{Priority: 64, ID: packetid.ClientboundLogin, F: e.onJoinGame}, - bot.PacketHandler{Priority: 64, ID: packetid.ClientboundChat, F: e.onChatMsg}, + //bot.PacketHandler{Priority: 64, ID: packetid.ClientboundChat, F: e.onChatMsg}, bot.PacketHandler{Priority: 64, ID: packetid.ClientboundDisconnect, F: e.onDisconnect}, bot.PacketHandler{Priority: 64, ID: packetid.ClientboundSetHealth, F: e.onUpdateHealth}, ) diff --git a/bot/client.go b/bot/client.go index 58a4d50..b827c18 100644 --- a/bot/client.go +++ b/bot/client.go @@ -2,13 +2,15 @@ package bot import ( "github.com/Tnze/go-mc/net" + "github.com/Tnze/go-mc/yggdrasil/userApi" "github.com/google/uuid" ) // Client is used to access Minecraft server type Client struct { - Conn *net.Conn - Auth Auth + Conn *net.Conn + Auth Auth + KeyPair userApi.KeyPairResp Name string UUID uuid.UUID diff --git a/bot/login.go b/bot/login.go index 77cf6b4..145ec40 100644 --- a/bot/login.go +++ b/bot/login.go @@ -10,6 +10,7 @@ import ( "crypto/x509" "encoding/json" "fmt" + "github.com/Tnze/go-mc/yggdrasil/userApi" "io" "net/http" "strings" @@ -18,7 +19,7 @@ import ( pk "github.com/Tnze/go-mc/net/packet" ) -// Auth includes a account +// Auth includes an account type Auth struct { Name string UUID string @@ -42,7 +43,7 @@ func handleEncryptionRequest(c *Client, p pk.Packet) error { // 响应加密请求 // Write Encryption Key Response - p, err = genEncryptionKeyResponse(key, er.PublicKey, er.VerifyToken) + p, err = genEncryptionKeyResponse(key, er.PublicKey, er.VerifyToken, c.KeyPair) if err != nil { return fmt.Errorf("gen encryption key response fail: %v", err) } @@ -177,7 +178,7 @@ func newSymmetricEncryption() (key []byte, encoStream, decoStream cipher.Stream) return } -func genEncryptionKeyResponse(shareSecret, publicKey, verifyToken []byte) (erp pk.Packet, err error) { +func genEncryptionKeyResponse(shareSecret, publicKey, verifyToken []byte, keyPair userApi.KeyPairResp) (erp pk.Packet, err error) { iPK, err := x509.ParsePKIXPublicKey(publicKey) // Decode Public Key if err != nil { err = fmt.Errorf("decode public key fail: %v", err) @@ -189,14 +190,19 @@ func genEncryptionKeyResponse(shareSecret, publicKey, verifyToken []byte) (erp p err = fmt.Errorf("encryption share secret fail: %v", err) return } + verifyT, err := rsa.EncryptPKCS1v15(rand.Reader, rsaKey, verifyToken) if err != nil { err = fmt.Errorf("encryption verfy tokenfail: %v", err) return } + + // currently broken + return pk.Marshal( 0x01, pk.ByteArray(cryptPK), + pk.Boolean(true), pk.ByteArray(verifyT), ), nil } diff --git a/bot/mcbot.go b/bot/mcbot.go index 0eff75a..77102d9 100644 --- a/bot/mcbot.go +++ b/bot/mcbot.go @@ -7,6 +7,7 @@ package bot import ( "context" "errors" + "github.com/Tnze/go-mc/yggdrasil/userApi" "net" "strconv" @@ -17,7 +18,7 @@ import ( ) // ProtocolVersion is the protocol version number of minecraft net protocol -const ProtocolVersion = 758 +const ProtocolVersion = 759 const DefaultPort = mcnet.DefaultPort // JoinServer connect a Minecraft server for playing the game. @@ -33,6 +34,7 @@ func (c *Client) JoinServerWithDialer(d *net.Dialer, addr string) (err error) { } func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { + const Handshake = 0x00 // Split Host and Port host, portStr, err := net.SplitHostPort(addr) @@ -70,16 +72,39 @@ func (c *Client) join(ctx context.Context, d *mcnet.Dialer, addr string) error { if err != nil { return LoginErr{"handshake", err} } - // Login Start - err = c.Conn.WritePacket(pk.Marshal( - packetid.LoginStart, - pk.String(c.Auth.Name), - )) + pair, err := userApi.GetOrFetchKeyPair(c.Auth.AsTk) if err != nil { - return LoginErr{"login start", err} + err = c.Conn.WritePacket(pk.Marshal( + packetid.LoginStart, + pk.String(c.Auth.Name), + pk.Boolean(false), + )) + if err != nil { + return LoginErr{"login start (without sig)", err} + } + } else { + // Login Start (Currently not support sig) + err = c.Conn.WritePacket(pk.Marshal( + packetid.LoginStart, + pk.String(c.Auth.Name), + pk.Boolean(false), + )) + //block, _ := pem.Decode([]byte(pair.KeyPair.PublicKey)) + //sig, _ := base64.StdEncoding.DecodeString(pair.PublicKeySignature) + //err = c.Conn.WritePacket(pk.Marshal( + // packetid.LoginStart, + // pk.String(c.Auth.Name), + // pk.Boolean(true), + // pk.Long(pair.ExpiresAt.UnixMilli()), + // pk.ByteArray(block.Bytes), + // pk.ByteArray(sig), + //)) + if err != nil { + return LoginErr{"login start (with sig)", err} + } + c.KeyPair = pair } - for { //Receive Packet var p pk.Packet diff --git a/yggdrasil/userApi/keyPair.go b/yggdrasil/userApi/keyPair.go new file mode 100644 index 0000000..7b5e35f --- /dev/null +++ b/yggdrasil/userApi/keyPair.go @@ -0,0 +1,25 @@ +package userApi + +import ( + "time" +) + +type KeyPairResp struct { + KeyPair struct { + PrivateKey string `json:"privateKey"` + PublicKey string `json:"publicKey"` + } `json:"keyPair"` + PublicKeySignature string `json:"publicKeySignature"` + ExpiresAt time.Time `json:"expiresAt"` + RefreshedAfter time.Time `json:"refreshedAfter"` +} + +func GetOrFetchKeyPair(accessToken string) (KeyPairResp, error) { + return fetchKeyPair(accessToken) // TODO: cache +} + +func fetchKeyPair(accessToken string) (KeyPairResp, error) { + var keyPairResp KeyPairResp + err := post("/player/certificates", accessToken, &keyPairResp) + return keyPairResp, err +} diff --git a/yggdrasil/userApi/userApi.go b/yggdrasil/userApi/userApi.go new file mode 100644 index 0000000..9d42f6e --- /dev/null +++ b/yggdrasil/userApi/userApi.go @@ -0,0 +1,40 @@ +package userApi + +import ( + "encoding/json" + "fmt" + "net/http" +) + +var ServicesURL = "https://api.minecraftservices.com" + +var client = http.DefaultClient + +func post(endpoint string, accessToken string, resp interface{}) error { + rowResp, err := rawPost(endpoint, accessToken) + if err != nil { + return fmt.Errorf("request fail: %v", err) + } + defer rowResp.Body.Close() + err = json.NewDecoder(rowResp.Body).Decode(resp) + if err != nil { + return fmt.Errorf("parse resp fail: %v", err) + } + + return nil +} + +func rawPost(endpoint string, accessToken string) (*http.Response, error) { + PostRequest, err := http.NewRequest( + http.MethodPost, + ServicesURL+endpoint, nil) + if err != nil { + return nil, fmt.Errorf("make request error: %v", err) + } + + PostRequest.Header.Set("Authorization", "Bearer "+accessToken) + PostRequest.Header.Set("Content-Type", "application/json; charset=utf-8") + + // Do + return client.Do(PostRequest) +}