183 lines
5.3 KiB
Go
183 lines
5.3 KiB
Go
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
|
|
}
|