working
This commit is contained in:
182
pkg/protocol/crypto.go
Normal file
182
pkg/protocol/crypto.go
Normal file
@@ -0,0 +1,182 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user