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[:]) }