Files
ts-mobile-go/go/_patches/github.com/honeybbq/teamspeak-go/handshake.go
T
2026-07-20 19:01:03 +08:00

191 lines
5.1 KiB
Go

package teamspeak
import (
"crypto/sha1" //nolint:gosec // SHA-1 used for TS3 HWID/UID format, not security
"encoding/base64"
"log/slog"
"strconv"
"github.com/honeybbq/teamspeak-go/commands"
"github.com/honeybbq/teamspeak-go/crypto"
"github.com/honeybbq/teamspeak-go/handshake"
"github.com/honeybbq/teamspeak-go/transport"
)
func (c *Client) handleHandshakeInitIV(cmd *commands.Command) {
c.logger.Info("received crypto negotiation")
alpha := cmd.Params["alpha"]
beta := cmd.Params["beta"]
omega := cmd.Params["omega"]
err := c.crypt.InitCrypto(alpha, beta, omega)
if err != nil {
c.logger.Error("failed to initialize crypto", slog.Any("error", err))
return
}
c.logger.Info("crypto initialized, sending clientinit")
c.sendClientInit()
}
func (c *Client) handleHandshakeExpand2(cmd *commands.Command) {
c.logger.Info("received initivexpand2")
c.handler.ReceivedFinalInitAck()
license := cmd.Params["l"]
omega := cmd.Params["omega"]
proof := cmd.Params["proof"]
beta := cmd.Params["beta"]
privateKey, err := c.sendClientEkPacket(beta)
if err != nil {
c.logger.Warn("failed to send clientek", slog.Any("error", err))
return
}
err = handshake.CryptoInit2(c.crypt, license, omega, proof, beta, privateKey)
if err != nil {
c.logger.Error("crypto init2 failed", slog.Any("error", err))
return
}
c.sendClientInit()
}
func (c *Client) sendClientEkPacket(beta string) ([]byte, error) {
publicKey, privateKey, err := crypto.GenerateTemporaryKey()
if err != nil {
c.logger.Error("failed to generate temporary key", slog.Any("error", err))
return nil, err
}
ekBase64 := base64.StdEncoding.EncodeToString(publicKey)
clientProof, err := c.buildClientEkProof(publicKey, beta)
if err != nil {
return nil, err
}
clientEk := commands.BuildCommandOrdered("clientek", [][2]string{
{"ek", ekBase64},
{"proof", clientProof},
})
c.logger.Debug("sending clientek", slog.String("ek", ekBase64))
err = c.handler.SendPacket(byte(transport.PacketTypeCommand), []byte(clientEk), 0)
if err != nil {
return nil, err
}
return privateKey, nil
}
func (c *Client) buildClientEkProof(publicKey []byte, beta string) (string, error) {
betaBytes, err := base64.StdEncoding.DecodeString(beta)
if err != nil {
c.logger.Error("failed to decode beta", slog.Any("error", err))
return "", err
}
toSign := make([]byte, 86)
copy(toSign, publicKey)
if len(betaBytes) > 54 {
betaBytes = betaBytes[:54]
}
copy(toSign[32:], betaBytes)
sign, err := crypto.Sign(c.crypt.Identity.PrivateKey, toSign)
if err != nil {
c.logger.Error("failed to sign clientek proof", slog.Any("error", err))
return "", err
}
return base64.StdEncoding.EncodeToString(sign), nil
}
func (c *Client) handleInitServer(cmd *commands.Command) {
c.mu.Lock()
c.status = StatusConnected
idStr := ""
if v, ok := cmd.Params["aclid"]; ok {
idStr = v
} else if v, ok := cmd.Params["clid"]; ok {
idStr = v
}
if idStr != "" {
val, err := strconv.ParseUint(idStr, 10, 16)
if err == nil {
c.clid = uint16(val)
c.handler.SetClientID(c.clid)
}
}
handlers := c.connectedHandlers
c.mu.Unlock()
c.logger.Info("connected to server", slog.Uint64("self_id", uint64(c.clid)))
c.connectedErr = nil
select {
case <-c.connectedChan:
default:
close(c.connectedChan)
}
go func() {
updateCmd := commands.BuildCommand("clientupdate", map[string]string{
"client_input_muted": "0",
"client_output_muted": "0",
})
_ = c.SendCommandNoWait(updateCmd)
}()
for _, h := range handlers {
go h()
}
}
func (c *Client) sendClientInit() {
cmd := c.buildClientInitCommand()
err := c.handler.SendPacket(byte(transport.PacketTypeCommand), []byte(cmd), 0)
if err != nil {
c.logger.Warn("failed to send clientinit", slog.Any("error", err))
}
}
func (c *Client) buildClientInitCommand() string {
pubKeyBase64 := c.crypt.Identity.PublicKeyBase64()
// HWID matches TS3 client UID format: base64(SHA1(publicKeyBase64))
hwidSum := sha1.Sum([]byte(pubKeyBase64)) //nolint:gosec
hwid := base64.StdEncoding.EncodeToString(hwidSum[:])
defaultChannelPassword := prepareClientPassword(c.clientInitOptions.defaultChannelPassword)
serverPassword := prepareClientPassword(c.clientInitOptions.serverPassword)
return commands.BuildCommandOrdered("clientinit", [][2]string{
{"client_nickname", c.nickname},
{"client_version", "3.?.? [Build: 5680278000]"},
{"client_platform", "Windows"},
{"client_input_hardware", "1"},
{"client_output_hardware", "1"},
{"client_default_channel", c.clientInitOptions.defaultChannel},
{"client_default_channel_password", defaultChannelPassword},
{"client_server_password", serverPassword},
{"client_meta_data", ""},
{"client_version_sign", "DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="},
{"client_key_offset", strconv.FormatUint(c.crypt.Identity.Offset, 10)},
{"client_nickname_phonetic", ""},
{"client_default_token", ""},
{"hwid", hwid},
})
}
func prepareClientPassword(password string) string {
if password == "" {
return ""
}
sum := sha1.Sum([]byte(password)) //nolint:gosec // TeamSpeak protocol requires base64(sha1(password))
return base64.StdEncoding.EncodeToString(sum[:])
}