首次推送
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
keySizeBytes = 16
|
||||
init1PacketType = 8
|
||||
hashInputMetaSize = 6
|
||||
clientSaltByte = 0x31
|
||||
serverSaltByte = 0x30
|
||||
)
|
||||
|
||||
// keyPool reuses 16-byte AES key buffers for packet crypto.
|
||||
var keyPool = sync.Pool{
|
||||
New: func() any {
|
||||
buf := make([]byte, keySizeBytes)
|
||||
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
// AcquireKeyBuffer returns a 16-byte buffer from keyPool or allocates one.
|
||||
func AcquireKeyBuffer() []byte {
|
||||
bufPtr, ok := keyPool.Get().(*[]byte)
|
||||
if !ok || bufPtr == nil {
|
||||
return make([]byte, keySizeBytes)
|
||||
}
|
||||
|
||||
return *bufPtr
|
||||
}
|
||||
|
||||
// ReleaseKeyBuffer returns buf to keyPool only if len(buf) is the AES key size.
|
||||
func ReleaseKeyBuffer(buf []byte) {
|
||||
if len(buf) == keySizeBytes {
|
||||
keyPool.Put(&buf)
|
||||
}
|
||||
}
|
||||
|
||||
// Precomputed dummy key/nonce matching the TS3 client pre-crypto placeholder.
|
||||
var (
|
||||
dummyKey = []byte("c:\\windows\\syste")
|
||||
dummyNonce = []byte("m\\firewall32.cpl")
|
||||
)
|
||||
|
||||
func (tc *Crypt) GetKeyNonce(
|
||||
fromServer bool,
|
||||
packetID uint16,
|
||||
generationID uint32,
|
||||
packetType byte,
|
||||
dummy bool,
|
||||
) ([]byte, []byte) {
|
||||
if dummy {
|
||||
key := AcquireKeyBuffer()
|
||||
copy(key, dummyKey)
|
||||
|
||||
return key, dummyNonce
|
||||
}
|
||||
|
||||
cacheKey := makeCacheKey(fromServer, packetType, generationID)
|
||||
|
||||
tc.keyMu.Lock()
|
||||
kn, ok := tc.CachedKeys[cacheKey]
|
||||
if !ok {
|
||||
tmpToHash := make([]byte, hashInputMetaSize+len(tc.IvStruct))
|
||||
if fromServer {
|
||||
tmpToHash[0] = serverSaltByte
|
||||
} else {
|
||||
tmpToHash[0] = clientSaltByte
|
||||
}
|
||||
tmpToHash[1] = packetType & packetTypeMask
|
||||
binary.BigEndian.PutUint32(tmpToHash[2:6], generationID)
|
||||
copy(tmpToHash[6:], tc.IvStruct)
|
||||
|
||||
hash := sha256.Sum256(tmpToHash)
|
||||
kn = KeyNonce{
|
||||
Key: append([]byte(nil), hash[0:keySizeBytes]...),
|
||||
Nonce: append([]byte(nil), hash[keySizeBytes:2*keySizeBytes]...),
|
||||
Gen: generationID,
|
||||
}
|
||||
tc.CachedKeys[cacheKey] = kn
|
||||
}
|
||||
tc.keyMu.Unlock()
|
||||
|
||||
key := AcquireKeyBuffer()
|
||||
copy(key, kn.Key)
|
||||
var packetIDBytes [2]byte
|
||||
binary.BigEndian.PutUint16(packetIDBytes[:], packetID)
|
||||
key[0] ^= packetIDBytes[0]
|
||||
key[1] ^= packetIDBytes[1]
|
||||
|
||||
return key, kn.Nonce
|
||||
}
|
||||
|
||||
var init1MAC = []byte("TS3INIT1")
|
||||
|
||||
var ErrFakeSignatureMismatch = errors.New("fake signature mismatch")
|
||||
|
||||
// Encrypt returns (ciphertext, MAC, err). Init1 and unencrypted packet types bypass EAX.
|
||||
func (tc *Crypt) Encrypt(
|
||||
packetType byte,
|
||||
packetID uint16,
|
||||
generationID uint32,
|
||||
header, plaintext []byte,
|
||||
dummy bool,
|
||||
unencrypted bool,
|
||||
) ([]byte, []byte, error) {
|
||||
if packetType == init1PacketType {
|
||||
return plaintext, init1MAC, nil
|
||||
}
|
||||
|
||||
if unencrypted {
|
||||
return plaintext, tc.FakeSignature, nil
|
||||
}
|
||||
|
||||
key, nonce := tc.GetKeyNonce(false, packetID, generationID, packetType, dummy)
|
||||
defer ReleaseKeyBuffer(key)
|
||||
|
||||
eax, err := NewEAX(key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ciphertext, mac, err := eax.Encrypt(nonce, header, plaintext)
|
||||
|
||||
return ciphertext, mac, err
|
||||
}
|
||||
|
||||
// Decrypt verifies and decrypts ciphertext; Init1 and unencrypted types pass through.
|
||||
func (tc *Crypt) Decrypt(
|
||||
packetType byte,
|
||||
packetID uint16,
|
||||
generationID uint32,
|
||||
header, ciphertext, tag []byte,
|
||||
dummy bool,
|
||||
unencrypted bool,
|
||||
) ([]byte, error) {
|
||||
if packetType == init1PacketType {
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
if unencrypted {
|
||||
if subtle.ConstantTimeCompare(tag[:fakeSignatureSize], tc.FakeSignature) != 1 {
|
||||
return nil, ErrFakeSignatureMismatch
|
||||
}
|
||||
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
key, nonce := tc.GetKeyNonce(true, packetID, generationID, packetType, dummy)
|
||||
defer ReleaseKeyBuffer(key)
|
||||
|
||||
eax, err := NewEAX(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return eax.Decrypt(nonce, header, ciphertext, tag)
|
||||
}
|
||||
Reference in New Issue
Block a user