diff --git a/mtproto/crypt/crypt.go b/mtproto/crypt/crypt.go new file mode 100644 index 0000000..6530f2b --- /dev/null +++ b/mtproto/crypt/crypt.go @@ -0,0 +1,97 @@ +package crypt + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/sha1" + "encoding/binary" + "io" + "net" + + "github.com/9seconds/mtg/mtproto/rpc" + "github.com/9seconds/mtg/wrappers" +) + +type CipherPurpose uint8 + +const ( + CipherPurposeClient CipherPurpose = iota + CipherPurposeServer +) + +var emptyIP = [4]byte{0x00, 0x00, 0x00, 0x00} + +func NewMiddleProxyCipherRWC(conn io.ReadWriteCloser, req *rpc.RPCNonceRequest, resp *rpc.RPCNonceResponse, client *net.TCPAddr, remote *net.TCPAddr, secret []byte) io.ReadWriteCloser { + encryptor := newCBCCipher(CipherPurposeClient, req, resp, client, remote, secret) + decryptor := newCBCCipher(CipherPurposeServer, req, resp, client, remote, secret) + + return wrappers.NewBlockCipherRWC(conn, encryptor, decryptor) +} + +func newCBCCipher(purpose CipherPurpose, req *rpc.RPCNonceRequest, resp *rpc.RPCNonceResponse, client *net.TCPAddr, remote *net.TCPAddr, secret []byte) cipher.BlockMode { + message := bytes.Buffer{} + message.Write(resp.Nonce[:]) + message.Write(req.Nonce[:]) + message.Write(req.CryptoTS[:]) + + clientIPv4 := emptyIP[:] + serverIPv4 := emptyIP[:] + if client.IP.To4() != nil { + clientIPv4 = reverseBytes(client.IP.To4()) + serverIPv4 = reverseBytes(remote.IP.To4()) + } + message.Write(serverIPv4) + + var port [2]byte + binary.LittleEndian.PutUint16(port[:], uint16(client.Port)) + message.Write(port[:]) + + switch purpose { + case CipherPurposeClient: + message.WriteString("CLIENT") + case CipherPurposeServer: + message.WriteString("SERVER") + default: + panic("Unexpected cipher purpose") + } + + message.Write(clientIPv4) + binary.LittleEndian.PutUint16(port[:], uint16(remote.Port)) + message.Write(port[:]) + message.Write(secret) + message.Write(resp.Nonce[:]) + + if client.IP.To4() == nil { + message.Write(client.IP.To16()) + message.Write(remote.IP.To16()) + } + message.Write(req.Nonce[:]) + + return makeCipher(message.Bytes()) +} + +func makeCipher(message []byte) cipher.BlockMode { + md5sum := md5.Sum(message[1:]) + sha1sum := sha1.Sum(message) + + key := append(md5sum[12:], sha1sum[:]...) + iv := md5.Sum(message[2:]) + + block, err := aes.NewCipher(key) + if err != nil { + panic("Cannot create cipher from the given key") + } + + return cipher.NewCBCEncrypter(block, iv[:]) +} + +func reverseBytes(data []byte) []byte { + rv := make([]byte, len(data)) + for k, v := range data { + rv[len(data)-1-k] = v + } + + return rv +}