working
This commit is contained in:
563
internal/client/client.go
Normal file
563
internal/client/client.go
Normal file
@@ -0,0 +1,563 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go-ts/pkg/protocol"
|
||||
"go-ts/pkg/transport"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
ID uint64
|
||||
ParentID uint64
|
||||
Name string
|
||||
Order uint64
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Conn *transport.TS3Conn
|
||||
Handshake *HandshakeState
|
||||
|
||||
Nickname string
|
||||
ClientID uint16
|
||||
|
||||
// Counters
|
||||
PacketIDCounterC2S uint16
|
||||
|
||||
// State
|
||||
Connected bool
|
||||
|
||||
// Server Data
|
||||
Channels map[uint64]*Channel
|
||||
}
|
||||
|
||||
func NewClient(nickname string) *Client {
|
||||
return &Client{
|
||||
Nickname: nickname,
|
||||
PacketIDCounterC2S: 1,
|
||||
Channels: make(map[uint64]*Channel),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Connect(address string) error {
|
||||
conn, err := transport.NewTS3Conn(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Conn = conn
|
||||
// Initialize handshake state
|
||||
hs, err := NewHandshakeState(c.Conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Handshake = hs
|
||||
|
||||
// Improve Identity Security Level to 8 (Standard Requirement)
|
||||
c.Handshake.ImproveSecurityLevel(8)
|
||||
|
||||
log.Println("Connected to UDP. Starting Handshake...")
|
||||
|
||||
// Start Handshake Flow
|
||||
// Step 0
|
||||
if err := c.Handshake.SendPacket0(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read Loop for Handshake
|
||||
timeout := time.After(5 * time.Second)
|
||||
|
||||
for !c.Connected {
|
||||
select {
|
||||
case pkt := <-c.Conn.PacketChan():
|
||||
if err := c.handlePacket(pkt); err != nil {
|
||||
log.Printf("Error handling packet: %v", err)
|
||||
}
|
||||
case <-timeout:
|
||||
return fmt.Errorf("connection timed out")
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("=== Connected! Now listening for server data... ===")
|
||||
|
||||
// Send Ping every 3 seconds
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
// KeepAlive Loop
|
||||
for {
|
||||
select {
|
||||
case pkt := <-c.Conn.PacketChan():
|
||||
if err := c.handlePacket(pkt); err != nil {
|
||||
log.Printf("Error handling packet: %v", err)
|
||||
}
|
||||
case <-ticker.C:
|
||||
// Send Ping
|
||||
c.PacketIDCounterC2S++
|
||||
ping := protocol.NewPacket(protocol.PacketTypePing, nil)
|
||||
ping.Header.PacketID = c.PacketIDCounterC2S
|
||||
ping.Header.ClientID = c.ClientID // Should be assigned by server usually, but we use 0 or what?
|
||||
|
||||
// Encrypt Ping (if past handshake)
|
||||
// For now, assuming unencrypted ping is ignored or we need to encrypt it if in full session
|
||||
// Protocol says: "Everything is encrypted"
|
||||
// Using correct keys...
|
||||
|
||||
// Actually handlePacket sends PONG. We need to Initiate PING?
|
||||
// Simplified: Just printing "Ping" for now, or just wait for server to Ping us.
|
||||
// The server usually pings. We must reply Pong.
|
||||
// BUT if we don't send anything, we might time out.
|
||||
// Let's rely on Server Pings for now, but remove the 5s exit timeout.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handlePacket(pkt *protocol.Packet) error {
|
||||
log.Printf("Received Packet: ID=%d, Type=%v, Len=%d", pkt.Header.PacketID, pkt.Header.PacketType(), len(pkt.Data))
|
||||
|
||||
switch pkt.Header.PacketType() {
|
||||
case protocol.PacketTypeInit1:
|
||||
return c.handleInit(pkt)
|
||||
case protocol.PacketTypeCommand:
|
||||
// Send ACK
|
||||
// Ack Data: PacketID of the packet we're acknowledging (2 bytes)
|
||||
ackData := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(ackData, pkt.Header.PacketID)
|
||||
|
||||
ack := protocol.NewPacket(protocol.PacketTypeAck, ackData)
|
||||
|
||||
// ACK header PacketID should match the packet being acknowledged
|
||||
ack.Header.PacketID = pkt.Header.PacketID
|
||||
|
||||
// ACKs for Command packets during handshake are encrypted with HandshakeKey
|
||||
key := protocol.HandshakeKey
|
||||
nonce := protocol.HandshakeNonce
|
||||
|
||||
// Meta for Client->Server: PID(2) + CID(2) + PT(1) = 5 bytes
|
||||
meta := make([]byte, 5)
|
||||
binary.BigEndian.PutUint16(meta[0:2], ack.Header.PacketID)
|
||||
binary.BigEndian.PutUint16(meta[2:4], ack.Header.ClientID) // ClientID (usually 0 during handshake)
|
||||
meta[4] = ack.Header.Type
|
||||
|
||||
encData, mac, _ := protocol.EncryptEAX(key, nonce, meta, ack.Data)
|
||||
ack.Data = encData
|
||||
copy(ack.Header.MAC[:], mac)
|
||||
log.Printf("Sending ACK for PacketID %d", pkt.Header.PacketID)
|
||||
|
||||
c.Conn.SendPacket(ack)
|
||||
|
||||
return c.handleCommand(pkt)
|
||||
case protocol.PacketTypeVoice:
|
||||
c.handleVoice(pkt)
|
||||
case protocol.PacketTypePing:
|
||||
// Respond with Pong
|
||||
pong := protocol.NewPacket(protocol.PacketTypePong, nil)
|
||||
pong.Header.PacketID = pkt.Header.PacketID // Acknowledgement
|
||||
pong.Header.MAC = pkt.Header.MAC // TODO: calculate real mac
|
||||
c.Conn.SendPacket(pong)
|
||||
case protocol.PacketTypeAck:
|
||||
// Server acknowledged our packet - ACKs are encrypted
|
||||
// Decrypt with HandshakeKey
|
||||
key := protocol.HandshakeKey
|
||||
nonce := protocol.HandshakeNonce
|
||||
|
||||
meta := make([]byte, 3) // Server->Client is 3 bytes
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
meta[2] = pkt.Header.Type
|
||||
|
||||
data, err := protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
|
||||
if err != nil {
|
||||
log.Printf("ACK decryption failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
ackPId := uint16(0)
|
||||
if len(data) >= 2 {
|
||||
ackPId = binary.BigEndian.Uint16(data[0:2])
|
||||
}
|
||||
log.Printf("Received ACK for PacketID %d", ackPId)
|
||||
|
||||
// If ACK is for clientek (PID=1), proceed with clientinit
|
||||
if ackPId == 1 && c.Handshake != nil && c.Handshake.Step == 5 {
|
||||
log.Println("clientek acknowledged! Sending clientinit...")
|
||||
c.Handshake.Step = 6
|
||||
return c.sendClientInit()
|
||||
}
|
||||
// If ACK is for clientinit (PID=2), we're connected!
|
||||
if ackPId == 2 && c.Handshake != nil && c.Handshake.Step == 6 {
|
||||
log.Println("clientinit acknowledged! Connection established!")
|
||||
c.Connected = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleInit(pkt *protocol.Packet) error {
|
||||
// Determine step based on packet content or local state
|
||||
// Simple state machine
|
||||
if c.Handshake.Step == 0 {
|
||||
if err := c.Handshake.HandlePacket1(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("Handshake Step 1 Completed. Sending Step 2...")
|
||||
return c.Handshake.SendPacket2()
|
||||
} else if c.Handshake.Step == 1 {
|
||||
// Wait, step 1 is processed, we sent step 2.
|
||||
// We expect Step 3.
|
||||
if pkt.Data[0] == 0x03 {
|
||||
if err := c.Handshake.HandlePacket3(pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("Handshake Step 3 Completed. Sending Step 4 (Puzzle Solution)...")
|
||||
// Send Packet 4 (Not fully implemented in this snippet due to puzzle complexity)
|
||||
// c.Handshake.SendPacket4()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleCommand(pkt *protocol.Packet) error {
|
||||
// Check if Encrypted
|
||||
// PacketTypeCommand is usually encrypted.
|
||||
// Flag check? The flag is in the Header (e.g. Unencrypted flag).
|
||||
// If Unencrypted flag is SET, it's cleartext.
|
||||
// Spec: "Command ... Encrypted: ✓". So Unencrypted flag is CLEARED.
|
||||
|
||||
// Decrypt if necessary
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
if pkt.Header.FlagUnencrypted() {
|
||||
data = pkt.Data
|
||||
} else {
|
||||
var key, nonce []byte
|
||||
decrypted := false
|
||||
|
||||
// 1. Try SharedSecret if available
|
||||
if c.Handshake != nil && c.Handshake.Step >= 6 && len(c.Handshake.SharedIV) > 0 {
|
||||
// Use SharedSecret-based encryption
|
||||
crypto := &protocol.CryptoState{
|
||||
SharedIV: c.Handshake.SharedIV,
|
||||
SharedMac: c.Handshake.SharedMac,
|
||||
GenerationID: 0,
|
||||
}
|
||||
// Server->Client = false
|
||||
key, nonce = crypto.GenerateKeyNonce(&pkt.Header, false)
|
||||
|
||||
// AAD for Server->Client: PacketID (2) + Type|Flags (1)
|
||||
meta := make([]byte, 3)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
meta[2] = pkt.Header.Type // Type includes Flags
|
||||
|
||||
data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
|
||||
if err == nil {
|
||||
decrypted = true
|
||||
} else {
|
||||
log.Printf("SharedSecret decrypt failed (PID=%d): %v. Trying HandshakeKey...", pkt.Header.PacketID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Fallback to HandshakeKey
|
||||
if !decrypted {
|
||||
key = protocol.HandshakeKey[:]
|
||||
nonce = protocol.HandshakeNonce[:]
|
||||
|
||||
// AAD matching KeyNonce derivation context?
|
||||
// HandshakeKey usage usually has same AAD requirements?
|
||||
meta := make([]byte, 3)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
meta[2] = pkt.Header.Type // Type includes Flags
|
||||
|
||||
data, err = protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
|
||||
if err != nil {
|
||||
log.Printf("All decryption attempts failed for PID=%d: %v", pkt.Header.PacketID, err)
|
||||
return fmt.Errorf("decryption failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// On first encrypted command set Connected = true (Fallback if ACK missed)
|
||||
if !c.Connected && pkt.Header.PacketID > 2 {
|
||||
c.Connected = true
|
||||
}
|
||||
|
||||
cmdStr := string(data)
|
||||
|
||||
// Fix Garbage Headers (TS3 often sends binary garbage before command)
|
||||
// Scan for first valid lower case [a-z] char (Most commands are lowercase)
|
||||
validStart := strings.IndexFunc(cmdStr, func(r rune) bool {
|
||||
return (r >= 'a' && r <= 'z')
|
||||
})
|
||||
|
||||
if validStart > 0 && validStart < 50 {
|
||||
cmdStr = cmdStr[validStart:]
|
||||
}
|
||||
|
||||
log.Printf("Command: %s", cmdStr)
|
||||
|
||||
// Parse Command
|
||||
cmd, args := protocol.ParseCommand([]byte(cmdStr))
|
||||
|
||||
switch cmd {
|
||||
case "initivexpand2":
|
||||
err := c.Handshake.ProcessInitivexpand2(args)
|
||||
if err != nil {
|
||||
log.Printf("Error processing initivexpand2: %v", err)
|
||||
}
|
||||
case "initserver":
|
||||
// Server sends this after clientinit - contains our clientID
|
||||
if cid, ok := args["aclid"]; ok {
|
||||
var id uint64
|
||||
fmt.Sscanf(cid, "%d", &id)
|
||||
c.ClientID = uint16(id)
|
||||
log.Printf("Assigned ClientID: %d", c.ClientID)
|
||||
}
|
||||
if name, ok := args["virtualserver_name"]; ok {
|
||||
log.Printf("Server Name: %s", protocol.Unescape(name))
|
||||
}
|
||||
case "channellist":
|
||||
// Parse channel info
|
||||
ch := &Channel{}
|
||||
if cid, ok := args["cid"]; ok {
|
||||
fmt.Sscanf(cid, "%d", &ch.ID)
|
||||
}
|
||||
if pid, ok := args["cpid"]; ok {
|
||||
fmt.Sscanf(pid, "%d", &ch.ParentID)
|
||||
}
|
||||
if name, ok := args["channel_name"]; ok {
|
||||
ch.Name = protocol.Unescape(name)
|
||||
}
|
||||
if order, ok := args["channel_order"]; ok {
|
||||
fmt.Sscanf(order, "%d", &ch.Order)
|
||||
}
|
||||
c.Channels[ch.ID] = ch
|
||||
log.Printf("Channel: [%d] NameRaw=%q Order=%d Args=%v", ch.ID, ch.Name, ch.Order, args)
|
||||
case "channellistfinished":
|
||||
log.Printf("=== Channel List Complete (%d channels) ===", len(c.Channels))
|
||||
var targetChan *Channel
|
||||
for _, ch := range c.Channels {
|
||||
log.Printf(" - [%d] %s (parent=%d)", ch.ID, ch.Name, ch.ParentID)
|
||||
if ch.Name == "Test" {
|
||||
targetChan = ch
|
||||
}
|
||||
}
|
||||
|
||||
if targetChan != nil {
|
||||
log.Printf("Found target channel 'Test' (ID=%d). Joining...", targetChan.ID)
|
||||
|
||||
if c.ClientID == 0 {
|
||||
log.Println("ERROR: ClientID is 0. Cannot join channel. 'initserver' missing?")
|
||||
return nil
|
||||
}
|
||||
|
||||
// clientmove clid={clid} cid={cid} cpw=
|
||||
cmd := fmt.Sprintf("clientmove clid=%d cid=%d cpw=", c.ClientID, targetChan.ID)
|
||||
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeCommand, []byte(cmd))
|
||||
|
||||
// Encrypt
|
||||
// key, nonce, mac, _ := c.getCryptoState() // Unused
|
||||
|
||||
// Meta
|
||||
meta := make([]byte, 5)
|
||||
binary.BigEndian.PutUint16(meta[0:2], c.PacketIDCounterC2S+1) // Next ID
|
||||
pkt.Header.PacketID = c.PacketIDCounterC2S + 1
|
||||
c.PacketIDCounterC2S++
|
||||
binary.BigEndian.PutUint16(meta[2:4], c.ClientID)
|
||||
meta[4] = pkt.Header.Type
|
||||
|
||||
// TODO: Use correct crypto keys (SharedSecret if available)
|
||||
// My getCryptoState returns correct ones?
|
||||
// Let's manually do it to match sendClientInit logic for now or refactor later.
|
||||
// Actually, if we are in Full Session, we should use SharedSecret.
|
||||
// Handshake Step 6 -> SharedSecret.
|
||||
|
||||
crypto := &protocol.CryptoState{
|
||||
SharedIV: c.Handshake.SharedIV,
|
||||
SharedMac: c.Handshake.SharedMac,
|
||||
GenerationID: 0,
|
||||
}
|
||||
k, n := crypto.GenerateKeyNonce(&pkt.Header, true)
|
||||
|
||||
encData, mac, _ := protocol.EncryptEAX(k, n, meta, pkt.Data)
|
||||
pkt.Data = encData
|
||||
copy(pkt.Header.MAC[:], mac)
|
||||
|
||||
c.Conn.SendPacket(pkt)
|
||||
}
|
||||
case "notifycliententerview":
|
||||
// A client entered the server
|
||||
if nick, ok := args["client_nickname"]; ok {
|
||||
log.Printf("Client entered: %s", protocol.Unescape(nick))
|
||||
}
|
||||
case "notifytextmessage":
|
||||
if msg, ok := args["msg"]; ok {
|
||||
log.Printf("Text Message: %s", protocol.Unescape(msg))
|
||||
}
|
||||
case "notifychannelgrouplist":
|
||||
// Ignore for now
|
||||
case "notifyservergrouplist":
|
||||
// Ignore for now
|
||||
case "notifyclientneededpermissions":
|
||||
// Ignore for now
|
||||
case "notifyclientleftview":
|
||||
if nick, ok := args["client_nickname"]; ok {
|
||||
log.Printf("Client left: %s", protocol.Unescape(nick))
|
||||
}
|
||||
case "notifyconnectioninfo":
|
||||
// Ignore
|
||||
case "badges":
|
||||
// Server badges info
|
||||
case "notifyclientchatcomposing":
|
||||
if nick, ok := args["client_nickname"]; ok {
|
||||
// This often comes as clid, need to lookup in future
|
||||
log.Printf("Client typing: %s", protocol.Unescape(nick))
|
||||
}
|
||||
case "notifyclientmoved":
|
||||
if nick, ok := args["client_nickname"]; ok {
|
||||
log.Printf("Client moved: %s", protocol.Unescape(nick))
|
||||
}
|
||||
case "error":
|
||||
if id, ok := args["id"]; ok && id == "522" {
|
||||
log.Println("WARNING: Server rejected client version (Error 522). Ignoring as requested.")
|
||||
// We pretend we are connected?
|
||||
// The server might not send further data, but we won't crash.
|
||||
c.Connected = true
|
||||
} else {
|
||||
log.Printf("Server Error: %v", args)
|
||||
}
|
||||
default:
|
||||
// Handle prefixes for weirdly updated commands
|
||||
if strings.HasPrefix(cmd, "badges") {
|
||||
// ignore badges garbage
|
||||
log.Println("Received Badges (Ignored)")
|
||||
return nil
|
||||
}
|
||||
// Log unknown commands for debugging
|
||||
log.Printf("Unhandled command: %s", cmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper to encrypt/decrypt based on state
|
||||
func (c *Client) getCryptoState() (key, nonce, mac []byte, isHandshake bool) {
|
||||
if c.Handshake != nil && len(c.Handshake.SharedSecret) > 0 {
|
||||
// Use Derived Keys
|
||||
// But we need to Generate Key/Nonce per packet!
|
||||
// This logic belongs in the Packet Encode/Decode flow or a higher level wrapper?
|
||||
return nil, nil, c.Handshake.SharedMac, false
|
||||
}
|
||||
return protocol.HandshakeKey, protocol.HandshakeNonce, protocol.HandshakeMac[:], true
|
||||
}
|
||||
|
||||
// Update encryption in Send/Receive
|
||||
// Packet handling needs to know WHICH key to use.
|
||||
// Simple rule:
|
||||
// - Init1 (Type 8): Handshake Keys (Unencrypted payload, but MAC is HandshakeMac)
|
||||
// - Command (Type 2): Encrypted.
|
||||
// - CommandLow (Type 3): Encrypted.
|
||||
// - Voice (Type 0): Encrypted.
|
||||
// - Ping/Pong: Encrypted.
|
||||
// - Ack: Encrypted.
|
||||
|
||||
// IF c.Handshake.SharedSecret is set, we SHOULD use it for Commands?
|
||||
// "The crypto handshake is now completed. The normal encryption scheme ... is from now on used."
|
||||
// This starts AFTER clientek? Or WITH clientek? "clientek already has the packet id 1"
|
||||
|
||||
func (c *Client) handleVoice(pkt *protocol.Packet) {
|
||||
// Parse Voice Header
|
||||
// 2 bytes VID, 1 byte Codec, Data
|
||||
if len(pkt.Data) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
// vid := binary.BigEndian.Uint16(pkt.Data[0:2])
|
||||
// codec := pkt.Data[2]
|
||||
voiceData := pkt.Data[3:]
|
||||
|
||||
// Calculate "Volume" (RMS of encrypted/compressed data is meaningless, but existing requirement asks for it)
|
||||
// To do this properly, we need to decrypt -> decode[Opus] -> PCM -> RMS.
|
||||
// For "Eco" (Echo), we can just re-wrap this data and send it back.
|
||||
|
||||
vol := len(voiceData) // Placeholder "volume"
|
||||
log.Printf("Voice Packet received. Approx Size/Vol: %d", vol)
|
||||
|
||||
// Echo back
|
||||
// Client -> Server Voice Packet
|
||||
// VID + Codec + Data
|
||||
// We can reuse the data payload structure mostly?
|
||||
// C->S: VID(2) + Codec(1) + Data
|
||||
|
||||
echoPkt := protocol.NewPacket(protocol.PacketTypeVoice, pkt.Data)
|
||||
// ID Counter handling?
|
||||
c.Conn.SendPacket(echoPkt)
|
||||
}
|
||||
|
||||
func (c *Client) sendClientInit() error {
|
||||
// Build clientinit command
|
||||
// Build clientinit command using TeamSpeak 3.6.2 credentials
|
||||
params := map[string]string{
|
||||
"client_nickname": c.Nickname,
|
||||
"client_version": "3.6.2 [Build: 1690976575]",
|
||||
"client_platform": "Windows",
|
||||
"client_input_hardware": "1",
|
||||
"client_output_hardware": "1",
|
||||
"client_default_channel": "",
|
||||
"client_default_channel_password": "",
|
||||
"client_server_password": "",
|
||||
"client_meta_data": "",
|
||||
"client_version_sign": "OyuLO/1bVJtBsXLRWzfGVhNaQd7B9D4QTolZm14DM1uCbSXVvqX3Ssym3sLi/PcvOl+SAUlX6NwBPOsQdwOGDw==",
|
||||
"client_key_offset": fmt.Sprintf("%d", c.Handshake.IdentityOffset),
|
||||
"client_nickname_phonetic": "",
|
||||
"client_default_token": "",
|
||||
"hwid": "1234567890",
|
||||
}
|
||||
|
||||
// Construct command string manually to ensure key correctness
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("clientinit")
|
||||
for k, v := range params {
|
||||
buf.WriteString(" ")
|
||||
buf.WriteString(k)
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(protocol.Escape(v))
|
||||
}
|
||||
cmd := buf.String()
|
||||
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeCommand, []byte(cmd))
|
||||
pkt.Header.PacketID = 2 // After clientek (1)
|
||||
pkt.Header.Type |= protocol.PacketFlagNewProtocol
|
||||
|
||||
// After clientek, use SharedSecret encryption (Now that we fixed derivation logic)
|
||||
crypto := &protocol.CryptoState{
|
||||
SharedIV: c.Handshake.SharedIV,
|
||||
SharedMac: c.Handshake.SharedMac,
|
||||
GenerationID: 0,
|
||||
}
|
||||
// Client->Server = true
|
||||
key, nonce := crypto.GenerateKeyNonce(&pkt.Header, true)
|
||||
|
||||
// AAD must match the header structure exactly (excluding MAC)
|
||||
// Client Header: PacketID (2) + ClientID (2) + Type|Flags (1)
|
||||
meta := make([]byte, 5)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID)
|
||||
|
||||
// Byte 4 is Type (which includes Flags)
|
||||
meta[4] = pkt.Header.Type
|
||||
|
||||
encData, mac, err := protocol.EncryptEAX(key, nonce, meta, pkt.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkt.Data = encData
|
||||
copy(pkt.Header.MAC[:], mac)
|
||||
|
||||
log.Println("Sending clientinit (Packet 2) [Encrypted with SharedSecret]...")
|
||||
return c.Conn.SendPacket(pkt)
|
||||
}
|
||||
504
internal/client/handshake.go
Normal file
504
internal/client/handshake.go
Normal file
@@ -0,0 +1,504 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
// Unused but placeholder
|
||||
"go-ts/pkg/protocol"
|
||||
"go-ts/pkg/transport"
|
||||
|
||||
"filippo.io/edwards25519"
|
||||
)
|
||||
|
||||
// HandshakeState tracks the progress of the connection initialization.
|
||||
type HandshakeState struct {
|
||||
Step int
|
||||
|
||||
// Init Cookies/Buffers
|
||||
A0 [4]byte
|
||||
A1 [16]byte
|
||||
A2 [100]byte
|
||||
|
||||
// Puzzle Data
|
||||
X *big.Int
|
||||
N *big.Int
|
||||
Level uint32
|
||||
|
||||
// Identity
|
||||
IdentityKey *ecdsa.PrivateKey
|
||||
Alpha []byte // 10 random bytes
|
||||
|
||||
// Server Data
|
||||
Beta []byte
|
||||
Omega []byte // Server Public Key (DER)
|
||||
License []byte
|
||||
|
||||
// Crypto
|
||||
ClientEkPub [32]byte
|
||||
ClientEkPriv [32]byte
|
||||
ClientScalar *edwards25519.Scalar // Client ephemeral private key (scalar)
|
||||
|
||||
SharedSecret []byte
|
||||
SharedIV []byte
|
||||
SharedMac []byte
|
||||
|
||||
IdentityOffset uint64 // Extracted/Mined offset
|
||||
IdentityLevel int
|
||||
|
||||
Conn *transport.TS3Conn
|
||||
}
|
||||
|
||||
func NewHandshakeState(conn *transport.TS3Conn) (*HandshakeState, error) {
|
||||
// Generate Identity On Startup (or load from disk in future)
|
||||
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate identity key: %v", err)
|
||||
}
|
||||
|
||||
alpha := make([]byte, 10)
|
||||
if _, err := rand.Read(alpha); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate alpha: %v", err)
|
||||
}
|
||||
|
||||
// Generate Client Ephemeral Key (TS3 Style)
|
||||
// TS3 uses a random 32-byte scalar with the high bit masked (&= 0x7F).
|
||||
// It does NOT use standard Ed25519 clamping (bits 0-2 cleared, bit 254 set).
|
||||
var clientScalar *edwards25519.Scalar
|
||||
var clientKeyBytes [32]byte
|
||||
|
||||
for {
|
||||
if _, err := rand.Read(clientKeyBytes[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clientKeyBytes[31] &= 0x7F // Mask high bit (positive scalar)
|
||||
|
||||
// Try to load as scalar. Might fail if >= L (very unlikely)
|
||||
clientScalar, err = new(edwards25519.Scalar).SetCanonicalBytes(clientKeyBytes[:])
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &HandshakeState{
|
||||
Step: 0,
|
||||
Conn: conn,
|
||||
IdentityKey: privKey,
|
||||
Alpha: alpha,
|
||||
ClientScalar: clientScalar,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ... SendPacket0, HandlePacket1, SendPacket2, HandlePacket3 logic unchanged ...
|
||||
// (Omitting for brevity if replacing, but user asked for full file usually.
|
||||
// Just including necessary parts and new methods)
|
||||
|
||||
func (h *HandshakeState) SendPacket0() error {
|
||||
buf := new(bytes.Buffer)
|
||||
ts := int32(time.Now().Unix()) - 1356998400
|
||||
binary.Write(buf, binary.BigEndian, ts)
|
||||
buf.WriteByte(0x00) // Step 0
|
||||
now := int32(time.Now().Unix())
|
||||
binary.Write(buf, binary.BigEndian, now)
|
||||
rand.Read(h.A0[:])
|
||||
buf.Write(h.A0[:])
|
||||
buf.Write(make([]byte, 8)) // 8 Zeros
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes())
|
||||
pkt.Header.PacketID = 101
|
||||
pkt.Header.ClientID = 0
|
||||
pkt.Header.MAC = protocol.HandshakeMac
|
||||
return h.Conn.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (h *HandshakeState) HandlePacket1(pkt *protocol.Packet) error {
|
||||
if len(pkt.Data) < 21 {
|
||||
return fmt.Errorf("packet 1 too short")
|
||||
}
|
||||
if pkt.Data[0] != 0x01 {
|
||||
return fmt.Errorf("expected step 1, got %d", pkt.Data[0])
|
||||
}
|
||||
copy(h.A1[:], pkt.Data[1:17])
|
||||
h.Step = 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HandshakeState) SendPacket2() error {
|
||||
buf := new(bytes.Buffer)
|
||||
ts := int32(time.Now().Unix()) - 1356998400
|
||||
binary.Write(buf, binary.BigEndian, ts)
|
||||
buf.WriteByte(0x02) // Step 2
|
||||
buf.Write(h.A1[:])
|
||||
a0Rev := [4]byte{h.A0[3], h.A0[2], h.A0[1], h.A0[0]}
|
||||
buf.Write(a0Rev[:])
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes())
|
||||
pkt.Header.PacketID = 102
|
||||
pkt.Header.MAC = protocol.HandshakeMac
|
||||
return h.Conn.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (h *HandshakeState) HandlePacket3(pkt *protocol.Packet) error {
|
||||
if len(pkt.Data) < 233 {
|
||||
return fmt.Errorf("packet 3 too short")
|
||||
}
|
||||
if pkt.Data[0] != 0x03 {
|
||||
return fmt.Errorf("expected step 3, got %d", pkt.Data[0])
|
||||
}
|
||||
h.X = new(big.Int).SetBytes(pkt.Data[1:65])
|
||||
h.N = new(big.Int).SetBytes(pkt.Data[65:129])
|
||||
h.Level = binary.BigEndian.Uint32(pkt.Data[129:133])
|
||||
copy(h.A2[:], pkt.Data[133:233])
|
||||
h.Step = 3
|
||||
log.Printf("Received Puzzle: Level=%d", h.Level)
|
||||
return h.SendPacket4()
|
||||
}
|
||||
|
||||
func (h *HandshakeState) SendPacket4() error {
|
||||
e := new(big.Int).Lsh(big.NewInt(1), uint(h.Level))
|
||||
y := new(big.Int).Exp(h.X, e, h.N)
|
||||
yBytes := y.Bytes()
|
||||
yPadded := make([]byte, 64)
|
||||
if len(yBytes) > 64 {
|
||||
copy(yPadded, yBytes[len(yBytes)-64:])
|
||||
} else {
|
||||
copy(yPadded[64-len(yBytes):], yBytes)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
ts := int32(time.Now().Unix()) - 1356998400
|
||||
binary.Write(buf, binary.BigEndian, ts)
|
||||
buf.WriteByte(0x04) // Step 4
|
||||
xPadded := make([]byte, 64)
|
||||
xb := h.X.Bytes()
|
||||
if len(xb) > 64 {
|
||||
copy(xPadded, xb[len(xb)-64:])
|
||||
} else {
|
||||
copy(xPadded[64-len(xb):], xb)
|
||||
}
|
||||
buf.Write(xPadded)
|
||||
nPadded := make([]byte, 64)
|
||||
nb := h.N.Bytes()
|
||||
if len(nb) > 64 {
|
||||
copy(nPadded, nb[len(nb)-64:])
|
||||
} else {
|
||||
copy(nPadded[64-len(nb):], nb)
|
||||
}
|
||||
buf.Write(nPadded)
|
||||
binary.Write(buf, binary.BigEndian, h.Level)
|
||||
buf.Write(h.A2[:])
|
||||
buf.Write(yPadded)
|
||||
cmdData := h.GenerateClientInitIV()
|
||||
buf.Write([]byte(cmdData))
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes())
|
||||
pkt.Header.PacketID = 103
|
||||
pkt.Header.MAC = protocol.HandshakeMac
|
||||
log.Println("Sending Packet 4 with Solution and clientinitiv...")
|
||||
return h.Conn.SendPacket(pkt)
|
||||
}
|
||||
|
||||
// ProcessInitivexpand2 handles the decrypted command logic
|
||||
func (h *HandshakeState) ProcessInitivexpand2(cmdArgs map[string]string) error {
|
||||
if h.Step >= 5 {
|
||||
log.Println("Ignoring duplicate initivexpand2 (Step already advanced)")
|
||||
return nil
|
||||
}
|
||||
|
||||
lStr, ok := cmdArgs["l"]
|
||||
if !ok {
|
||||
return errors.New("missing license (l)")
|
||||
}
|
||||
|
||||
lData, err := base64.StdEncoding.DecodeString(lStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
betaStr, ok := cmdArgs["beta"]
|
||||
if !ok {
|
||||
return errors.New("missing beta")
|
||||
}
|
||||
h.Beta, err = base64.StdEncoding.DecodeString(betaStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 1. Derive Server Public Key (Edwards Y)
|
||||
serverEdPubBytes, err := protocol.ParseLicenseAndDeriveKey(lData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("LICENSE FAIL: %v", err)
|
||||
}
|
||||
|
||||
// Load Server Public Key as Edwards Point
|
||||
serverPoint, err := new(edwards25519.Point).SetBytes(serverEdPubBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid server public key point: %v", err)
|
||||
}
|
||||
|
||||
// 2. Client Ephemeral Key (Scalar) is pre-generated in NewHandshakeState (h.ClientScalar)
|
||||
// Compute Client Public Key (Point) = Scalar * Base
|
||||
clientPubPoint := new(edwards25519.Point).ScalarBaseMult(h.ClientScalar)
|
||||
h.ClientEkPub = [32]byte(clientPubPoint.Bytes())
|
||||
|
||||
// 3. Calculate Shared Secret (Ed25519 Scalar Mult)
|
||||
|
||||
// Negate Server Public Key (TS3/Punisher.NaCl logic)
|
||||
serverPointNeg := new(edwards25519.Point).Negate(serverPoint)
|
||||
|
||||
// Multiply: Result = Scalar * (-ServerPoint)
|
||||
sharedPoint := new(edwards25519.Point).ScalarMult(h.ClientScalar, serverPointNeg)
|
||||
sharedBytes := sharedPoint.Bytes()
|
||||
|
||||
// XOR the last byte with 0x80 (Flip sign bit of X coordinate)
|
||||
sharedBytes[31] ^= 0x80
|
||||
|
||||
// 4. SHA512 Hash of the result
|
||||
hash := sha512.Sum512(sharedBytes)
|
||||
h.SharedSecret = hash[:]
|
||||
|
||||
h.SharedIV = make([]byte, 64)
|
||||
copy(h.SharedIV, h.SharedSecret)
|
||||
|
||||
// XOR operations
|
||||
// SharedIV[0..10] xor alpha
|
||||
// SharedIV[10..64] xor beta
|
||||
|
||||
// Alpha is 10 bytes (h.Alpha)
|
||||
for i := 0; i < 10; i++ {
|
||||
h.SharedIV[i] ^= h.Alpha[i]
|
||||
}
|
||||
|
||||
// Beta should be 54 bytes
|
||||
log.Printf("Debug - Beta Length: %d", len(h.Beta))
|
||||
if len(h.Beta) >= 54 {
|
||||
for i := 0; i < 54; i++ {
|
||||
h.SharedIV[10+i] ^= h.Beta[i]
|
||||
}
|
||||
}
|
||||
|
||||
// SharedMac = SHA1(SharedIV)[0..8]
|
||||
macHash := sha1.Sum(h.SharedIV)
|
||||
copy(h.SharedMac[:], macHash[0:8])
|
||||
|
||||
log.Printf("Debug - SharedSecret (SHA512): %s", hex.EncodeToString(h.SharedSecret))
|
||||
log.Printf("Debug - SharedIV: %s", hex.EncodeToString(h.SharedIV))
|
||||
log.Printf("Debug - SharedMac: %s", hex.EncodeToString(h.SharedMac[:]))
|
||||
log.Println("Shared Secret & Keys Calculated using TS3 Ed25519 logic.")
|
||||
|
||||
return h.SendClientEk()
|
||||
}
|
||||
|
||||
func (h *HandshakeState) SendClientEk() error {
|
||||
// clientek ek={ek} proof={proof}
|
||||
|
||||
// ek = base64(client_public_key) [Ed25519 Compressed Point]
|
||||
ekStr := base64.StdEncoding.EncodeToString(h.ClientEkPub[:])
|
||||
|
||||
// proof = base64(ecdsa_sign(client_public_key + beta))
|
||||
// "sign must be done with the private key from the identity keypair." (P-256)
|
||||
|
||||
proofBuf := append(h.ClientEkPub[:], h.Beta...)
|
||||
hash := sha256.Sum256(proofBuf)
|
||||
|
||||
r, s, err := ecdsa.Sign(rand.Reader, h.IdentityKey, hash[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Encode Signature (ASN.1 DER)
|
||||
// Reverting to DER as server uses DER.
|
||||
|
||||
sigContent := new(bytes.Buffer)
|
||||
writeBigInt(sigContent, r)
|
||||
writeBigInt(sigContent, s)
|
||||
|
||||
sigSeq := new(bytes.Buffer)
|
||||
sigSeq.WriteByte(0x30)
|
||||
writeLength(sigSeq, sigContent.Len())
|
||||
sigSeq.Write(sigContent.Bytes())
|
||||
|
||||
proofBytes := sigSeq.Bytes()
|
||||
proofStr := base64.StdEncoding.EncodeToString(proofBytes)
|
||||
|
||||
log.Printf("Debug - ClientEk: %s", ekStr)
|
||||
log.Printf("Debug - Proof (DER): %s", hex.EncodeToString(proofBytes))
|
||||
|
||||
// Construct Command
|
||||
cmd := fmt.Sprintf("clientek ek=%s proof=%s", protocol.Escape(ekStr), protocol.Escape(proofStr))
|
||||
|
||||
// Packet 1 (New counter logic? Spec: "clientek already has the packet id 1")
|
||||
// This is the START of the new encrypted session?
|
||||
// "The normal packet id counting starts with this packet."
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write([]byte(cmd))
|
||||
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeCommand, buf.Bytes())
|
||||
pkt.Header.PacketID = 1 // Reset to 1
|
||||
pkt.Header.SetType(protocol.PacketTypeCommand) // Ensure flag (NewProtocol? Unencrypted?)
|
||||
|
||||
// Encryption?
|
||||
// "All Command ... Packets must get encrypted." (Using OLD Handshake keys? Or NEW?)
|
||||
// "The crypto handshake is now completed. The normal encryption scheme ... is from now on used."
|
||||
// Usually implies clientek IS encrypted with the NEW keys.
|
||||
|
||||
// Add PacketFlagNewProtocol
|
||||
pkt.Header.Type |= protocol.PacketFlagNewProtocol // 0x20
|
||||
|
||||
// Encryption
|
||||
// Try using HandshakeKey like initivexpand2 instead of SharedSecret
|
||||
// The crypto switch might happen AFTER clientek is accepted
|
||||
key := protocol.HandshakeKey
|
||||
nonce := protocol.HandshakeNonce
|
||||
|
||||
// Prepare Meta for EAX
|
||||
// Meta for Client->Server: PID(2) + CID(2) + PT(1) = 5 bytes
|
||||
meta := make([]byte, 5)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID)
|
||||
meta[4] = pkt.Header.Type
|
||||
|
||||
// Encrypt
|
||||
cipherDict, mac, err := protocol.EncryptEAX(key, nonce, meta, pkt.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkt.Data = cipherDict
|
||||
copy(pkt.Header.MAC[:], mac)
|
||||
|
||||
log.Println("Sending clientek (Packet 1) [Encrypted]")
|
||||
h.Step = 5
|
||||
return h.Conn.SendPacket(pkt)
|
||||
}
|
||||
|
||||
func (h *HandshakeState) GenerateClientInitIV() string {
|
||||
// ... (Existing implementation) ...
|
||||
// Copy from previous step
|
||||
|
||||
alphaStr := base64.StdEncoding.EncodeToString(h.Alpha)
|
||||
omegaStr := h.GenerateOmega()
|
||||
|
||||
ip := "127.0.0.1"
|
||||
if addr, ok := h.Conn.RemoteAddr().(*net.UDPAddr); ok {
|
||||
ip = addr.IP.String()
|
||||
} else if h.Conn.RemoteAddr() != nil {
|
||||
ip = h.Conn.RemoteAddr().String()
|
||||
if host, _, err := net.SplitHostPort(ip); err == nil {
|
||||
ip = host
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("clientinitiv alpha=%s omega=%s ot=1 ip=%s",
|
||||
protocol.Escape(alphaStr), protocol.Escape(omegaStr), protocol.Escape(ip))
|
||||
}
|
||||
|
||||
func (h *HandshakeState) GenerateOmega() string {
|
||||
// ... (Existing implementation) ...
|
||||
pub := h.IdentityKey.PublicKey
|
||||
|
||||
content := new(bytes.Buffer)
|
||||
content.Write([]byte{0x03, 0x02, 0x07, 0x00})
|
||||
content.Write([]byte{0x02, 0x01, 0x20})
|
||||
writeBigInt(content, pub.X)
|
||||
writeBigInt(content, pub.Y)
|
||||
seq := new(bytes.Buffer)
|
||||
seq.WriteByte(0x30)
|
||||
writeLength(seq, content.Len())
|
||||
seq.Write(content.Bytes())
|
||||
return base64.StdEncoding.EncodeToString(seq.Bytes())
|
||||
}
|
||||
|
||||
func writeBigInt(buf *bytes.Buffer, n *big.Int) {
|
||||
b := n.Bytes()
|
||||
buf.WriteByte(0x02)
|
||||
padded := b
|
||||
if len(b) > 0 && b[0] >= 0x80 {
|
||||
padded = make([]byte, len(b)+1)
|
||||
padded[0] = 0x00
|
||||
copy(padded[1:], b)
|
||||
} else if len(b) == 0 {
|
||||
padded = []byte{0x00}
|
||||
}
|
||||
writeLength(buf, len(padded))
|
||||
buf.Write(padded)
|
||||
}
|
||||
|
||||
func writeLength(buf *bytes.Buffer, length int) {
|
||||
if length < 128 {
|
||||
buf.WriteByte(byte(length))
|
||||
} else {
|
||||
s := fmt.Sprintf("%x", length)
|
||||
if len(s)%2 != 0 {
|
||||
s = "0" + s
|
||||
}
|
||||
b, _ := hex.DecodeString(s)
|
||||
buf.WriteByte(0x80 | byte(len(b)))
|
||||
buf.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
// ImproveSecurityLevel mines a counter to achieve the target security level.
|
||||
func (h *HandshakeState) ImproveSecurityLevel(targetLevel int) {
|
||||
omega := h.GenerateOmega() // Base64 of ASN.1 Public Key
|
||||
|
||||
// Start from current offset (usually 0)
|
||||
counter := h.IdentityOffset
|
||||
|
||||
fmt.Printf("Mining Identity Level %d... ", targetLevel)
|
||||
|
||||
for {
|
||||
// Construct data: Omega + Counter (ASCII)
|
||||
data := fmt.Sprintf("%s%d", omega, counter)
|
||||
|
||||
// SHA1
|
||||
hash := sha1.Sum([]byte(data))
|
||||
|
||||
// Count leading zero bits
|
||||
zeros := countLeadingZeros(hash[:])
|
||||
|
||||
if zeros >= targetLevel {
|
||||
h.IdentityLevel = zeros
|
||||
h.IdentityOffset = counter
|
||||
fmt.Printf("Found! Offset=%d, Level=%d\n", counter, zeros)
|
||||
return
|
||||
}
|
||||
|
||||
counter++
|
||||
if counter%100000 == 0 {
|
||||
// fmt.Print(".")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func countLeadingZeros(hash []byte) int {
|
||||
zeros := 0
|
||||
for _, b := range hash {
|
||||
if b == 0 {
|
||||
zeros += 8
|
||||
} else {
|
||||
// Count bits in this byte
|
||||
for i := 7; i >= 0; i-- {
|
||||
if (b>>i)&1 == 0 {
|
||||
zeros++
|
||||
} else {
|
||||
return zeros
|
||||
}
|
||||
}
|
||||
return zeros
|
||||
}
|
||||
}
|
||||
return zeros
|
||||
}
|
||||
Reference in New Issue
Block a user