package echo_go import ( "bytes" "crypto/aes" "crypto/rand" "encoding/hex" "encoding/json" "fmt" "log" "net" "strings" "git.vh7.uk/jakew/echo-go/crypto" ) const EchoVersion = "3.17" func randomHex(n int) (string, error) { b := make([]byte, n) if _, err := rand.Read(b); err != nil { return "", err } return hex.EncodeToString(b), nil } func (c *Client) SendPlain(msgType MessageType, data *string, subType *string, metadata []string) error { metadataBytes, err := json.Marshal(&metadata) if err != nil { return err } msg := RawMessage{ UserId: c.UserId, MessageType: msgType, SubType: subType, Data: data, Metadata: string(metadataBytes), } b, err := json.Marshal(&msg) if err != nil { return err } //log.Printf("sending message %v", msg) _, err = c.Con.Write(b) return err } func (c *Client) Send(msgType MessageType, data *string, subType *string, metadata []string) error { metadataBytes, err := json.Marshal(&metadata) if err != nil { return err } msg := RawMessage{ UserId: c.UserId, MessageType: msgType, SubType: subType, Data: data, Metadata: string(metadataBytes), } plainBytes, err := json.Marshal(&msg) if err != nil { return err } aesCipher, err := aes.NewCipher([]byte(c.SessionKey)) if err != nil { return err } encryptedBytes, err := crypto.EncryptAesCbc(aesCipher, plainBytes) if err != nil { return err } _, err = c.Con.Write(encryptedBytes) return err } func (c *Client) ReceivePlain() ([]RawMessage, error) { data := make([]byte, 20480) _, err := c.Con.Read(data) if err != nil { return nil, err } messages := []RawMessage{} for _, message := range strings.Split(string(bytes.Trim(data, "\x00")), "}") { if strings.TrimSpace(message) == "" { continue } var msg RawMessage err = json.Unmarshal([]byte(message+"}"), &msg) if err != nil { return nil, err } messages = append(messages, msg) } return messages, nil } func (c *Client) Receive() ([]RawMessage, error) { data := make([]byte, 20480) _, err := c.Con.Read(data) if err != nil { return nil, err } messages := []RawMessage{} for _, message := range strings.Split(string(bytes.Trim(data, "\x00")), "]") { if strings.TrimSpace(message) == "" { continue } aesCipher, err := aes.NewCipher([]byte(c.SessionKey)) if err != nil { return nil, err } decrypted, err := crypto.DecryptAesCbc(aesCipher, []byte(message+"]")) if err != nil { return nil, err } var msg RawMessage err = json.Unmarshal(decrypted, &msg) if err != nil { return nil, err } messages = append(messages, msg) } return messages, nil } func (c *Client) handshakeLoop(password string) error { log.Println("sending server info request") err := c.SendPlain(ReqServerInfo, nil, nil, nil) if err != nil { return err } encrypted := false for { var msgs []RawMessage var err error if encrypted { msgs, err = c.Receive() } else { msgs, err = c.ReceivePlain() } if err != nil { return err } for _, msg := range msgs { switch msg.MessageType { case ResServerInfo: ciphertext, err := crypto.RsaEncrypt([]byte(*msg.Data), []byte(c.SessionKey)) err = c.SendPlain(ReqClientSecret, &ciphertext, nil, nil) if err != nil { return err } encrypted = true case ResClientSecret: data, err := json.Marshal([]string{ c.Username, password, EchoVersion, }) if err != nil { return err } dataStr := string(data) err = c.Send(ReqConnection, &dataStr, nil, nil) if err != nil { return err } case ResConnectionAccepted: log.Println("handshake accepted") return nil case ResConnectionDenied: return fmt.Errorf("handshake failed - %v", *msg.Data) default: return fmt.Errorf("unexpected handshake message type %v", msg.MessageType) } } } } func (c *Client) Disconnect() { log.Println("gracefully disconnecting") _ = c.Send(ReqDisconnect, nil, nil, nil) _ = c.Con.Close() } func New(addr string, username string) (*Client, error) { userId, err := randomHex(32) if err != nil { return nil, err } sessionKey, err := randomHex(8) if err != nil { return nil, err } log.Printf("connecting to %v", addr) con, err := net.Dial("tcp", addr) if err != nil { return nil, err } client := Client{ Con: con, UserId: userId, SessionKey: sessionKey, Username: username, } return &client, nil }