package protocol import ( "crypto/aes" "crypto/cipher" "crypto/sha1" "crypto/sha256" "encoding/base64" "encoding/binary" "fmt" ) // Crypto Globals for Handshake (as per spec) var ( HandshakeMac = [8]byte{0x54, 0x53, 0x33, 0x49, 0x4E, 0x49, 0x54, 0x31} // "TS3INIT1" HandshakeKey = []byte{0x63, 0x3A, 0x5C, 0x77, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x73, 0x5C, 0x73, 0x79, 0x73, 0x74, 0x65} // "c:\windows\syste" HandshakeNonce = []byte{0x6D, 0x5C, 0x66, 0x69, 0x72, 0x65, 0x77, 0x61, 0x6C, 0x6C, 0x33, 0x32, 0x2E, 0x63, 0x70, 0x6C} // "m\firewall32.cpl" ) // EAX Mode Implementation (AES-CTR + OMAC) -> For simplicity we will use a simplified approach or find a library if possible. // Since Go doesn't have EAX in standard lib, and we want to avoid complex dependencies if possible for this snippet, // we'll implement the key generation logic. The EAX encryption usually wraps AES-CTR. // Important: For a production client, use a verified crypto library. type CryptoState struct { SharedIV []byte SharedMac []byte GenerationID uint32 } // GenerateKeyNonce generates the Key and Nonce for EAX encryption/decryption as per section 1.6.2 func (s *CryptoState) GenerateKeyNonce(header *PacketHeader, isClientToServer bool) ([]byte, []byte) { // 1. Temporary buffer // length depends on protocol version/logic. // Spec: "The old protocol SharedIV ... will be 20 bytes long ... new protocol SharedIV ... will have 64 bytes" // GenerationID starts at 0. var temp []byte sivLen := len(s.SharedIV) if sivLen == 20 { temp = make([]byte, 26) } else { // Spec 1.6.2 New Protocol: 1 (type) + 1 (pt) + 4 (pgid) + 64 (sharedIV) = 70 bytes. temp = make([]byte, 70) } // temp[0] 'c'/'s' logic: // Spec: "0x30 for Client, 0x31 for Server... Wait. // "Used for Client -> Server: 0x31" // "Used for Server -> Client: 0x30" if isClientToServer { temp[0] = 0x31 } else { // Client <- Server temp[0] = 0x30 } // temp[1] = PT (Base Type only, no flags!) // ts3j uses getType().getIndex() which is the enum value. temp[1] = header.Type & 0x0F // temp[2..6] = PGId (Network Order) binary.BigEndian.PutUint32(temp[2:6], s.GenerationID) // Copy SharedIV // if SIV.length == 20 -> temp[6..26] = SIV[0..20] // if SIV.length == 64 -> temp[6..70] = SIV[0..64] copy(temp[6:], s.SharedIV) // keynonce = sha256(temp) hash := sha256.Sum256(temp) key := make([]byte, 16) nonce := make([]byte, 16) copy(key, hash[0:16]) copy(nonce, hash[16:32]) // XOR Key with PID // key[0] = key[0] xor ((PId & 0xFF00) >> 8) // key[1] = key[1] xor ((PId & 0x00FF) >> 0) key[0] ^= byte((header.PacketID & 0xFF00) >> 8) key[1] ^= byte((header.PacketID & 0x00FF)) return key, nonce } // Base64 Helpers for TS3 func Base64Encode(data []byte) string { return base64.StdEncoding.EncodeToString(data) } func Base64Decode(s string) ([]byte, error) { return base64.StdEncoding.DecodeString(s) } // SHA1 Helper func Sha1(data []byte) []byte { h := sha1.New() h.Write(data) return h.Sum(nil) } // GenerateHashCash implements the puzzle solver (Section 4.1) // Finds the level of a public key + offset func GetHashCashLevel(key []byte, offset uint64) int { // data = sha1(publicKey + keyOffset) // Note: Reference implementation concatenates the string representation of offset? // Spec says: "The key offset is a u64 number, which gets converted to a string when concatenated." offsetStr := fmt.Sprintf("%d", offset) buf := append(key, []byte(offsetStr)...) hash := Sha1(buf) // Count leading zero bits level := 0 for _, b := range hash { if b == 0 { level += 8 } else { // Count bits in this byte for i := 7; i >= 0; i-- { if (b & (1 << i)) == 0 { level++ } else { return level } } break } } return level } // Fake encryption for now to proceed until EAX is fully implemented or needed // The spec requires EAX for commands. func EncryptPacket(pkt *Packet, key, nonce []byte) error { // Placeholder: In a real implementation, we would use an EAX implementation here. // For the initial handshake (Init1 packets), encryption is often disabled or simplified (XOR). // Reviewing: Init1 packets are NOT encrypted (FlagUnencrypted set). // Command packets ARE encrypted. block, err := aes.NewCipher(key) if err != nil { return err } // Standard CTR implementation (part of EAX) // EAX components: CTR for encryption, OMAC for authentication (MAC) // We strictly need the MAC for the packet header. // For minimal functional requirement "Connect", we might need full EAX if the server enforces it on the first Command. // For now we will implement standard CTR for the data payload. // The MAC calculation is complex and requires OMAC1. stream := cipher.NewCTR(block, nonce) stream.XORKeyStream(pkt.Data, pkt.Data) return nil } func DecryptPacket(pkt *Packet, key, nonce []byte) error { block, err := aes.NewCipher(key) if err != nil { return err } stream := cipher.NewCTR(block, nonce) stream.XORKeyStream(pkt.Data, pkt.Data) return nil } // Curve25519 Helper func GenerateECDHKeypair() (public, private [32]byte, err error) { // Generate private key (use real random in prod) // For reproduction/dev we might use fixed. // Actually we need `crypto/rand` // private = rand(32) // curve25519.ScalarBaseMult(&public, &private) return }