191 lines
5.1 KiB
Go
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[:])
|
|
}
|