sungrow/sungrow.go
Josh Deprez dc10d3b6de typo
2021-01-11 13:59:50 +11:00

141 lines
3.5 KiB
Go

package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"fmt"
"io"
"net"
"sync"
)
const (
privateKey = "Grow#0*2Sun68CbE" // 16 bytes
noCrypto1 = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" // 16 bytes
noCrypto2 = "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" // 16 bytes
getKeyMsg = "\x68\x68\x00\x00\x00\x06\xf7\x04\x0a\xe7\x00\x08"
header = "\x68\x68"
padding = noCrypto2 // all FF bytes
)
func getKey(conn io.ReadWriter) ([]byte, error) {
if _, err := conn.Write([]byte(getKeyMsg)); err != nil {
return nil, err
}
kp := make([]byte, 25)
if _, err := io.ReadFull(conn, kp); err != nil {
return nil, err
}
key := kp[9:] // must be 16 bytes long, because maths
if bytes.Equal(key, []byte(noCrypto1)) || bytes.Equal(key, []byte(noCrypto2)) {
return nil, nil
}
for i := range key {
key[i] ^= privateKey[i]
}
return key, nil
}
// sungrowConn wraps a regular TCP connection with funky Sungrow encryption.
type sungrowConn struct {
net.Conn
block cipher.Block
fifo *bytes.Buffer
mu sync.RWMutex
txid uint16
}
func (c *sungrowConn) Write(msg []byte) (int, error) {
if c.block == nil {
return c.Conn.Write(msg)
}
c.mu.Lock()
defer c.mu.Unlock()
c.txid = binary.BigEndian.Uint16(msg[:2])
c.fifo.Truncate(0)
bs := c.block.BlockSize()
padlen := bs - (len(msg) % bs)
if padlen == bs {
padlen = 0
}
req := make([]byte, 4+len(msg)+padlen)
copy(req, []byte{1, 0, byte(len(msg)), byte(padlen)}) // 4 byte encryption header
copy(req[4:], header) // 2 byte sungrow header, replaces txid
copy(req[6:], msg[2:]) // rest of message
copy(req[4+len(msg):], padding[:padlen]) // padding
// ECB mode.........
for cp := req[4:]; len(cp) > 0; cp = cp[bs:] {
c.block.Encrypt(cp, cp)
}
n, err := c.Conn.Write(req)
return n - 4 - padlen, err
}
func (c *sungrowConn) Read(out []byte) (int, error) {
if c.block == nil {
return c.Conn.Read(out)
}
if c.fifo.Len() > 0 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.fifo.Read(out)
}
// 4-byte header describes how much to read
hdr := make([]byte, 4)
if _, err := io.ReadFull(c.Conn, hdr); err != nil {
return 0, err
}
pktlen, padlen := int(hdr[2]), int(hdr[3])
bs := c.block.BlockSize()
if (pktlen+padlen)%bs != 0 {
return 0, fmt.Errorf("pktlen + padlen = %d + %d = %d, want divisible by %d", pktlen, padlen, pktlen+padlen, bs)
}
pkt := make([]byte, pktlen+padlen)
if _, err := io.ReadFull(c.Conn, pkt); err != nil {
return 0, err
}
// ECB mode here too.........
for cp := pkt; len(cp) > 0; cp = cp[bs:] {
c.block.Decrypt(cp, cp)
}
c.mu.RLock()
defer c.mu.RUnlock()
binary.BigEndian.PutUint16(pkt, c.txid)
c.fifo.Write(pkt[:pktlen])
return c.fifo.Read(out)
}
// dialSungrow dials a TCP connection, obtains the encryption key, and returns
// a sungrowConn that encrypts/decrypts using the key.
func dialSungrow(addr string) (*sungrowConn, error) {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, fmt.Errorf("dialing: %w", err)
}
key, err := getKey(conn)
if err != nil {
return nil, fmt.Errorf("obtaining key: %w", err)
}
if len(key) == 0 { // no encryption
return &sungrowConn{Conn: conn}, nil
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("setting cipher: %w", err)
}
return &sungrowConn{
Conn: conn,
block: block,
fifo: new(bytes.Buffer),
}, nil
}