128 lines
2.6 KiB
Go
128 lines
2.6 KiB
Go
|
package steam
|
||
|
|
||
|
import (
|
||
|
"crypto/aes"
|
||
|
"crypto/cipher"
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"net"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/Philipp15b/go-steam/cryptoutil"
|
||
|
. "github.com/Philipp15b/go-steam/protocol"
|
||
|
)
|
||
|
|
||
|
type connection interface {
|
||
|
Read() (*Packet, error)
|
||
|
Write([]byte) error
|
||
|
Close() error
|
||
|
SetEncryptionKey([]byte)
|
||
|
IsEncrypted() bool
|
||
|
}
|
||
|
|
||
|
const tcpConnectionMagic uint32 = 0x31305456 // "VT01"
|
||
|
|
||
|
type tcpConnection struct {
|
||
|
conn *net.TCPConn
|
||
|
ciph cipher.Block
|
||
|
cipherMutex sync.RWMutex
|
||
|
}
|
||
|
|
||
|
func dialTCP(laddr, raddr *net.TCPAddr) (*tcpConnection, error) {
|
||
|
conn, err := net.DialTCP("tcp", laddr, raddr)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return &tcpConnection{
|
||
|
conn: conn,
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (c *tcpConnection) Read() (*Packet, error) {
|
||
|
// All packets begin with a packet length
|
||
|
var packetLen uint32
|
||
|
err := binary.Read(c.conn, binary.LittleEndian, &packetLen)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// A magic value follows for validation
|
||
|
var packetMagic uint32
|
||
|
err = binary.Read(c.conn, binary.LittleEndian, &packetMagic)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if packetMagic != tcpConnectionMagic {
|
||
|
return nil, fmt.Errorf("Invalid connection magic! Expected %d, got %d!", tcpConnectionMagic, packetMagic)
|
||
|
}
|
||
|
|
||
|
buf := make([]byte, packetLen, packetLen)
|
||
|
_, err = io.ReadFull(c.conn, buf)
|
||
|
if err == io.ErrUnexpectedEOF {
|
||
|
return nil, io.EOF
|
||
|
}
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
// Packets after ChannelEncryptResult are encrypted
|
||
|
c.cipherMutex.RLock()
|
||
|
if c.ciph != nil {
|
||
|
buf = cryptoutil.SymmetricDecrypt(c.ciph, buf)
|
||
|
}
|
||
|
c.cipherMutex.RUnlock()
|
||
|
|
||
|
return NewPacket(buf)
|
||
|
}
|
||
|
|
||
|
// Writes a message. This may only be used by one goroutine at a time.
|
||
|
func (c *tcpConnection) Write(message []byte) error {
|
||
|
c.cipherMutex.RLock()
|
||
|
if c.ciph != nil {
|
||
|
message = cryptoutil.SymmetricEncrypt(c.ciph, message)
|
||
|
}
|
||
|
c.cipherMutex.RUnlock()
|
||
|
|
||
|
err := binary.Write(c.conn, binary.LittleEndian, uint32(len(message)))
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
err = binary.Write(c.conn, binary.LittleEndian, tcpConnectionMagic)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
_, err = c.conn.Write(message)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func (c *tcpConnection) Close() error {
|
||
|
return c.conn.Close()
|
||
|
}
|
||
|
|
||
|
func (c *tcpConnection) SetEncryptionKey(key []byte) {
|
||
|
c.cipherMutex.Lock()
|
||
|
defer c.cipherMutex.Unlock()
|
||
|
if key == nil {
|
||
|
c.ciph = nil
|
||
|
return
|
||
|
}
|
||
|
if len(key) != 32 {
|
||
|
panic("Connection AES key is not 32 bytes long!")
|
||
|
}
|
||
|
|
||
|
var err error
|
||
|
c.ciph, err = aes.NewCipher(key)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (c *tcpConnection) IsEncrypted() bool {
|
||
|
c.cipherMutex.RLock()
|
||
|
defer c.cipherMutex.RUnlock()
|
||
|
return c.ciph != nil
|
||
|
}
|