这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions minecraft/auth/live.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"os"
"sync"
"time"

"golang.org/x/oauth2"
Expand Down Expand Up @@ -78,7 +79,8 @@ func RequestLiveTokenWriter(w io.Writer) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
_, _ = w.Write([]byte(fmt.Sprintf("Authenticate at %v using the code %v.\n", d.VerificationURI, d.UserCode)))

_, _ = fmt.Fprintf(w, "Authenticate at %v using the code %v.\n", d.VerificationURI, d.UserCode)
ticker := time.NewTicker(time.Second * time.Duration(d.Interval))
defer ticker.Stop()

Expand All @@ -97,6 +99,28 @@ func RequestLiveTokenWriter(w io.Writer) (*oauth2.Token, error) {
panic("unreachable")
}

var (
serverTimeMu sync.Mutex
// serverTime represents the most recent server date received from Microsoft servers.
// It's used for the signed requests which can be blocked if the users device time is not synced.
// It uses the date received from the unsigned requests.
serverTime time.Time
)

func updateServerTimeFromHeaders(headers http.Header) {
date := headers.Get("Date")
if date == "" {
return
}
t, err := time.Parse(time.RFC1123, date)
if err != nil || t.IsZero() {
return
}
serverTimeMu.Lock()
serverTime = t
serverTimeMu.Unlock()
}

// startDeviceAuth starts the device auth, retrieving a login URI for the user and a code the user needs to
// enter.
func startDeviceAuth() (*deviceAuthConnect, error) {
Expand Down Expand Up @@ -128,13 +152,17 @@ func pollDeviceAuth(deviceCode string) (t *oauth2.Token, err error) {
return nil, fmt.Errorf("POST https://login.live.com/oauth20_token.srf: %w", err)
}
defer resp.Body.Close()

updateServerTimeFromHeaders(resp.Header)

poll := new(deviceAuthPoll)
if err := json.NewDecoder(resp.Body).Decode(poll); err != nil {
return nil, fmt.Errorf("POST https://login.live.com/oauth20_token.srf: json decode: %w", err)
}
if poll.Error == "authorization_pending" {
switch poll.Error {
case "authorization_pending":
return nil, nil
} else if poll.Error == "" {
case "":
return &oauth2.Token{
AccessToken: poll.AccessToken,
TokenType: poll.TokenType,
Expand All @@ -160,6 +188,9 @@ func refreshToken(t *oauth2.Token) (*oauth2.Token, error) {
return nil, fmt.Errorf("POST https://login.live.com/oauth20_token.srf: %w", err)
}
defer resp.Body.Close()

updateServerTimeFromHeaders(resp.Header)

poll := new(deviceAuthPoll)
if err := json.NewDecoder(resp.Body).Decode(poll); err != nil {
return nil, fmt.Errorf("POST https://login.live.com/oauth20_token.srf: json decode: %w", err)
Expand Down
23 changes: 19 additions & 4 deletions minecraft/auth/xbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func obtainXBLToken(ctx context.Context, c *http.Client, key *ecdsa.PrivateKey,
data, err := json.Marshal(map[string]any{
"AccessToken": "t=" + liveToken.AccessToken,
"AppId": "0000000048183522",
"deviceToken": device.Token,
"DeviceToken": device.Token,
"Sandbox": "RETAIL",
"UseModernGamertag": true,
"SiteName": "user.auth.xboxlive.com",
Expand Down Expand Up @@ -102,6 +102,9 @@ func obtainXBLToken(ctx context.Context, c *http.Client, key *ecdsa.PrivateKey,
return nil, fmt.Errorf("POST %v: %w", "https://sisu.xboxlive.com/authorize", err)
}
defer resp.Body.Close()

updateServerTimeFromHeaders(resp.Header)

if resp.StatusCode != 200 {
// Xbox Live returns a custom error code in the x-err header.
if errorCode := resp.Header.Get("x-err"); errorCode != "" {
Expand Down Expand Up @@ -155,6 +158,9 @@ func obtainDeviceToken(ctx context.Context, c *http.Client, key *ecdsa.PrivateKe
if err != nil {
return nil, fmt.Errorf("POST %v: %w", "https://device.auth.xboxlive.com/device/authenticate", err)
}

updateServerTimeFromHeaders(resp.Header)

defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("POST %v: %v", "https://device.auth.xboxlive.com/device/authenticate", resp.Status)
Expand All @@ -166,7 +172,16 @@ func obtainDeviceToken(ctx context.Context, c *http.Client, key *ecdsa.PrivateKe
// sign signs the request passed containing the body passed. It signs the request using the ECDSA private key
// passed. If the request has a 'ProofKey' field in the Properties field, that key must be passed here.
func sign(request *http.Request, body []byte, key *ecdsa.PrivateKey) {
currentTime := windowsTimestamp()
serverTimeMu.Lock()
currentServerDate := serverTime
serverTimeMu.Unlock()
var currentTime int64
if !currentServerDate.IsZero() {
currentTime = windowsTimestamp(currentServerDate)
} else { // Should never happen
currentTime = windowsTimestamp(time.Now())
}

hash := sha256.New()

// Signature policy version (0, 0, 0, 1) + 0 byte.
Expand Down Expand Up @@ -214,8 +229,8 @@ func sign(request *http.Request, body []byte, key *ecdsa.PrivateKey) {

// windowsTimestamp returns a Windows specific timestamp. It has a certain offset from Unix time which must be
// accounted for.
func windowsTimestamp() int64 {
return (time.Now().Unix() + 11644473600) * 10000000
func windowsTimestamp(t time.Time) int64 {
return (t.Unix() + 11644473600) * 10000000
}

// padTo32Bytes converts a big.Int into a fixed 32-byte, zero-padded slice.
Expand Down
5 changes: 3 additions & 2 deletions minecraft/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,18 +357,19 @@ func getXBLToken(ctx context.Context, dialer Dialer) (*auth.XBLToken, error) {
if err != nil {
return nil, fmt.Errorf("request Live Connect token: %w", err)
}

xblToken, err := auth.RequestXBLToken(ctx, liveToken, "https://multiplayer.minecraft.net/")
if err != nil {
return nil, fmt.Errorf("request XBOX Live token: %w", err)
}

return xblToken, nil
}

// authChain requests the Minecraft auth JWT chain using the credentials passed. If successful, an encoded
// chain ready to be put in a login request is returned.
func authChain(ctx context.Context, xblToken *auth.XBLToken, key *ecdsa.PrivateKey) (string, error) {

// Obtain the raw chain data using the
// Obtain the raw chain data using the XBL token.
chain, err := auth.RequestMinecraftChain(ctx, xblToken, key)
if err != nil {
return "", fmt.Errorf("request Minecraft auth chain: %w", err)
Expand Down