From 85e0e95a4b6f7a3face2753677247e1a286a81f6 Mon Sep 17 00:00:00 2001 From: Qian Wang Date: Thu, 22 Nov 2018 21:56:29 +0000 Subject: [PATCH] User control server and client --- cmd/ck-client/admin.go | 156 ++++++++++++++++++++++ internal/server/usermanager/controller.go | 142 ++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 cmd/ck-client/admin.go create mode 100644 internal/server/usermanager/controller.go diff --git a/cmd/ck-client/admin.go b/cmd/ck-client/admin.go new file mode 100644 index 0000000..e651fc1 --- /dev/null +++ b/cmd/ck-client/admin.go @@ -0,0 +1,156 @@ +//build !android + +package main + +// TODO: rewrite this. Think of another way of admin control + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "log" + "net" + + "github.com/cbeuw/Cloak/internal/client" + "github.com/cbeuw/Cloak/internal/client/TLS" + "github.com/cbeuw/Cloak/internal/util" +) + +type UserInfo struct { + UID []byte + // ALL of the following fields have to be accessed atomically + SessionsCap uint32 + UpRate int64 + DownRate int64 + UpCredit int64 + DownCredit int64 + ExpiryTime int64 +} + +type administrator struct { + adminConn net.Conn + adminUID []byte +} + +func adminHandshake(sta *client.State) *administrator { + fmt.Println("Enter the ip:port of your server") + var addr string + fmt.Scanln(&addr) + fmt.Println("Enter the admin UID") + var b64AdminUID string + fmt.Scanln(&b64AdminUID) + adminUID, err := base64.StdEncoding.DecodeString(b64AdminUID) + if err != nil { + log.Println(err) + return nil + } + + sta.UID = adminUID + + remoteConn, err := net.Dial("tcp", addr) + if err != nil { + log.Println(err) + return nil + } + + clientHello := TLS.ComposeInitHandshake(sta) + _, err = remoteConn.Write(clientHello) + + // Three discarded messages: ServerHello, ChangeCipherSpec and Finished + discardBuf := make([]byte, 1024) + for c := 0; c < 3; c++ { + _, err = util.ReadTLS(remoteConn, discardBuf) + if err != nil { + log.Printf("Reading discarded message %v: %v\n", c, err) + return nil + } + } + + reply := TLS.ComposeReply() + _, err = remoteConn.Write(reply) + a := &administrator{remoteConn, adminUID} + return a +} + +func (a *administrator) getCommand() []byte { + fmt.Println("Select your command") + var cmd string + fmt.Scanln(&cmd) + switch cmd { + case "1": + return a.request([]byte{0x01}) + case "2": + return a.request([]byte{0x02}) + case "3": + fmt.Println("Enter UID") + var b64UID string + fmt.Scanln(&b64UID) + UID, _ := base64.StdEncoding.DecodeString(b64UID) + return a.request(append([]byte{0x03}, UID...)) + case "4": + var uinfo UserInfo + var b64UID string + fmt.Scanln(&b64UID) + UID, _ := base64.StdEncoding.DecodeString(b64UID) + uinfo.UID = UID + fmt.Scanf("%d", &uinfo.SessionsCap) + fmt.Scanf("%d", &uinfo.UpRate) + fmt.Scanf("%d", &uinfo.DownRate) + fmt.Scanf("%d", &uinfo.UpCredit) + fmt.Scanf("%d", &uinfo.DownCredit) + fmt.Scanf("%d", &uinfo.ExpiryTime) + marshed, _ := json.Marshal(uinfo) + return a.request(append([]byte{0x04}, marshed...)) + default: + return nil + } +} + +// protocol: 0[TLS record layer 5 bytes]5[IV 16 bytes]21[data][hmac 32 bytes] +func (a *administrator) request(data []byte) []byte { + dataLen := len(data) + + buf := make([]byte, 5+16+dataLen+32) + buf[0] = 0x17 + buf[1] = 0x03 + buf[2] = 0x03 + binary.BigEndian.PutUint16(buf[3:5], uint16(16+dataLen+32)) + + rand.Read(buf[5:21]) //iv + copy(buf[21:], data) + block, _ := aes.NewCipher(a.adminUID[0:16]) + stream := cipher.NewCTR(block, buf[5:21]) + stream.XORKeyStream(buf[21:21+dataLen], buf[21:21+dataLen]) + + mac := hmac.New(sha256.New, a.adminUID[16:32]) + mac.Write(buf[5 : 21+dataLen]) + copy(buf[21+dataLen:], mac.Sum(nil)) + + return buf +} + +var ErrInvalidMac = errors.New("Mac mismatch") + +func (a *administrator) checkAndDecrypt(data []byte) ([]byte, error) { + macIndex := len(data) - 32 + mac := hmac.New(sha256.New, a.adminUID[16:32]) + mac.Write(data[5:macIndex]) + expected := mac.Sum(nil) + if !hmac.Equal(data[macIndex:], expected) { + return nil, ErrInvalidMac + } + + iv := data[5:21] + ret := data[21:macIndex] + block, _ := aes.NewCipher(a.adminUID[0:16]) + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(ret, ret) + return ret, nil +} diff --git a/internal/server/usermanager/controller.go b/internal/server/usermanager/controller.go new file mode 100644 index 0000000..fb09b71 --- /dev/null +++ b/internal/server/usermanager/controller.go @@ -0,0 +1,142 @@ +package usermanager + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/json" + "errors" + "log" +) + +/* +0 reserved +1 listActiveUsers none []uids +2 listAllUsers none []userinfo +3 getUserInfo uid userinfo + +4 addNewUser userinfo ok +5 delUser uid ok +6 syncMemFromDB uid ok + +7 setSessionsCap uid cap ok +8 setUpRate uid rate ok +9 setDownRate uid rate ok +10 setUpCredit uid credit ok +11 setDownCredit uid credit ok +12 setExpiryTime uid time ok +13 addUpcredit uid delta ok +14 addDownCredit uid delta ok +*/ + +type controller struct { + *Userpanel + adminUID []byte +} + +func (up *Userpanel) MakeController(adminUID []byte) *controller { + return &controller{up, adminUID} +} + +func (c *controller) HandleRequest(req []byte) ([]byte, error) { + plain, err := c.checkAndDecrypt(req) + if err == ErrInvalidMac { + log.Printf("!!!CONTROL MESSAGE AND HMAC MISMATCH!!!\n raw request:\n%x\ndecrypted msg:\n%x", req, plain) + return nil, err + } + + switch plain[0] { + case 1: + UIDs := c.listActiveUsers() + resp, _ := json.Marshal(UIDs) + return c.respond(resp), nil + case 2: + uinfos := c.listAllUsers() + resp, _ := json.Marshal(uinfos) + return c.respond(resp), nil + case 3: + uinfo, err := c.getUserInfo(plain[1:33]) + if err != nil { + return c.respond([]byte(err.Error())), nil + } + resp, _ := json.Marshal(uinfo) + return c.respond(resp), nil + case 4: + var uinfo UserInfo + err = json.Unmarshal(plain[1:], &uinfo) + if err != nil { + return c.respond([]byte(err.Error())), nil + } + + err = c.addNewUser(uinfo) + if err != nil { + return c.respond([]byte(err.Error())), nil + } else { + return c.respond([]byte("ok")), nil + } + case 5: + err = c.delUser(plain[1:]) + if err != nil { + return c.respond([]byte(err.Error())), nil + } else { + return c.respond([]byte("ok")), nil + } + + case 6: + err = c.syncMemFromDB(plain[1:33]) + if err != nil { + return c.respond([]byte(err.Error())), nil + } else { + return c.respond([]byte("ok")), nil + } + // TODO: implement the rest + default: + return c.respond([]byte("Unsupported action")), nil + + } + +} + +var ErrInvalidMac = errors.New("Mac mismatch") + +// protocol: [TLS record layer 5 bytes][IV 16 bytes][data][hmac 32 bytes] +func (c *controller) respond(resp []byte) []byte { + respLen := len(resp) + + buf := make([]byte, 5+16+respLen+32) + buf[0] = 0x17 + buf[1] = 0x03 + buf[2] = 0x03 + PutUint16(buf[3:5], uint16(16+respLen+32)) + + rand.Read(buf[5:21]) //iv + copy(buf[21:], resp) + block, _ := aes.NewCipher(c.adminUID[0:16]) + stream := cipher.NewCTR(block, buf[5:21]) + stream.XORKeyStream(buf[21:21+respLen], buf[21:21+respLen]) + + mac := hmac.New(sha256.New, c.adminUID[16:32]) + mac.Write(buf[5 : 21+respLen]) + copy(buf[21+respLen:], mac.Sum(nil)) + + return buf +} + +func (c *controller) checkAndDecrypt(data []byte) ([]byte, error) { + macIndex := len(data) - 32 + mac := hmac.New(sha256.New, c.adminUID[16:32]) + mac.Write(data[5:macIndex]) + expected := mac.Sum(nil) + if !hmac.Equal(data[macIndex:], expected) { + return nil, ErrInvalidMac + } + + iv := data[5:21] + ret := data[21:macIndex] + block, _ := aes.NewCipher(c.adminUID[0:16]) + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(ret, ret) + return ret, nil +}