From 5ede3186e53a22280dc29261ff7d0e8c33a8748e Mon Sep 17 00:00:00 2001 From: Zack Scholl Date: Thu, 28 Jun 2018 06:38:07 -0700 Subject: [PATCH] first attempt --- README.md | 140 +++++------------------------------ main.go | 10 +++ src/logging.go | 43 +++++++++++ src/models.go | 51 +++++++++++++ src/server.go | 194 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 318 insertions(+), 120 deletions(-) create mode 100644 main.go create mode 100644 src/logging.go create mode 100644 src/server.go diff --git a/README.md b/README.md index 3359047..6ef2415 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -https://medium.com/@simplyianm/why-gos-structs-are-superior-to-class-based-inheritance-b661ba897c67 - ## Protocol @@ -7,38 +5,19 @@ Every GET/POST request should check the IP address and make sure that there are 1. **Sender** requests new channel and receives empty channel from **Relay**, or obtains the channel they request (or an error if it is already occupied). - POST /open - (optional) + POST /join { "channel": "...", // optional - "role": "sender", - "success": true, - "message": "got channel" - } - Returns: - { - "channel": "...", - "uuid": "...", - "success": true, - "message": "got channel" + "curve": "pxxx", // optional + "role": "sender" } 2. **Sender** generates *X* using PAKE from secret *pw*. 3. **Sender** sends *X* to **Relay** and the type of curve being used. Returns error if channel is already occupied by sender, otherwise it uses it. - POST /channel/:channel - { - "uuid": "...", - "x": "...", - "curve": "p557" - } - Returns: - { - "success": true, - "message": "updated x, curve" - } - + POST /channel { "x": "..." } + Note: posting to channel always requires UUID and channel for validation. 4. **Sender** communicates channel + secret *pw* to **Recipient** (human interaction). @@ -46,127 +25,46 @@ Every GET/POST request should check the IP address and make sure that there are 5. **Recipient** requests *X* from **Relay** using the channel. Returns error if it doesn't exist yet. - - GET /channel/:channel - Returns: - { - ... all information - "success": true, - "message": "updated x" - } + POST /channel (returns current state) 6. **Recipient** generates *Y*, session key *k_B*, and hashed session key *H(k_B)* using PAKE from secret *pw*. 7. **Recipient** sends *Y*, *H(H(k_B))* to **Relay**. - ``` - POST /channel/:channel + POST /channel { "y": "...", "hh_k": "..." } - { - "uuid": "...", - "y": "...", - "hh_k": "..." - } - Returns: - { - "success": true, - "message": "updated y" - } - ``` -7. **Sender** requests *Y*, *H(H(k_B))* from **Relay**. +8. **Sender** requests *Y*, *H(H(k_B))* from **Relay**. - ``` - GET /sender/:channel/y + POST /channel - Returns: - { - "y": "...", - "hh_k": "...", - "success": true, - "message": "got y" - } - ``` 8. **Sender** uses *Y* to generate its session key *k_A* and *H(k_A)*, and checks *H(H(k_A))*==*H(H(k_B))*. **Sender** aborts here if it is incorrect. 9. **Sender** gives the **Relay** authentication *H(k_A)*. - ``` - POST /sender/:channel/h_k - { - "h_k": "..." - } + POST /channel { "h_k": "..." } - Returns: - { - "success": true, - "message": "updated h_k" - } - ``` 10. **Recipient** requests *H(k_A)* from relay and checks against its own. If it doesn't match, then bail. - ``` - GET /recipient/:channel/h_k + POST /channel - Returns: - { - "h_k": "...", - "success": true, - "message": "got h_k" - } - ``` -11. **Sender** requests that **Relay** creates open TCP connection with itself as sender, identified by *H(k_A)*. +11. **Sender** connects to **Relay** tcp ports and identifies itself using channel+UUID. - ``` - GET /sender/:channel/open - - Returns: - { - "success": true, - "message": "opened channel" - } - ``` 12. **Sender** encrypts data with *k*. -13. **Recipient** requests that **Relay** creates open TCP connection with itself as recipient, identified by *H(k_B)*. +13. **Recipient** connects to **Relay** tcp ports and identifies itself using channel+UUID. - ``` - GET /recipient/:channel/open +14. **Relay** realizes it has both recipient and sender for the same channel so it staples their connections. Sets *stapled* to `true`. - Returns: - { - "success": true, - "message": "opened channel" - } - ``` -this will save the IP address as the reciever -14. **Recipient** starts listening to Relay. (Relay accepts **Recipient** because it knows **Recipient**'s IP address). +16. **Sender** asks **Relay** whether connections are stapled. -15. **Relay**, when it has a sender and recipient identified for TCP connections, staples the connections together. + POST /channel -16. **Sender** asks **Relay** whether the recipient is ready and connections are stapled. - - ``` - GET /sender/:channel/isready - - Returns: - { - "ready": true, - "success": true, - "message": "is ready" - } - ``` 17. **Sender** sends data over TCP. 18. **Recipient** closes relay when finished. Anyone participating in the channel can close the relay at any time. Any of the routes except the first ones will return errors if stuff doesn't exist. - ``` - GET /close/:channel - Returns: - { - "success": true, - "message": "closed" - } - ``` + POST /channel { "close": true } + @@ -183,6 +81,8 @@ https://github.com/tscholl2/siec - use functional options - every GET/POST request should check the IP address and make sure that there are never more than 2 IP addresses using a single channel +https://medium.com/@simplyianm/why-gos-structs-are-superior-to-class-based-inheritance-b661ba897c67 + croc.New() croc.SetX().... Set parameters diff --git a/main.go b/main.go new file mode 100644 index 0000000..5791014 --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +package main + +import croc "github.com/schollz/croc/src" + +func main() { + err := croc.RunRelay("8002") + if err != nil { + panic(err) + } +} diff --git a/src/logging.go b/src/logging.go new file mode 100644 index 0000000..1e65cff --- /dev/null +++ b/src/logging.go @@ -0,0 +1,43 @@ +package croc + +import ( + log "github.com/cihub/seelog" +) + +// SetLogLevel determines the log level +func SetLogLevel(level string) (err error) { + + // https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit + // https://github.com/cihub/seelog/wiki/Log-levels + appConfig := ` + + + + + + + + + + + + + + + + + + + + + + + + ` + logger, err := log.LoggerFromConfigAsBytes([]byte(appConfig)) + if err != nil { + return + } + log.ReplaceLogger(logger) + return +} diff --git a/src/models.go b/src/models.go index cb9a9b5..cbb1556 100644 --- a/src/models.go +++ b/src/models.go @@ -1 +1,52 @@ package croc + +import "crypto/elliptic" + +type channelData struct { + // public + Name string `json:"name,omitempty"` + State map[string][]byte + + // private + stapled bool + uuids [2]string // 0 is sender, 1 is recipient + curve elliptic.Curve +} + +type response struct { + // various responses + Channel string `json:"channel,omitempty"` + UUID string `json:"uuid,omitempty"` + State map[string][]byte `json:"state,omitempty"` + + // constant responses + Success bool `json:"success"` + Message string `json:"message"` +} + +type payloadOpen struct { + Channel string `json:"channel"` + Role int `json:"role"` + Curve string `json:"curve"` +} + +type payloadChannel struct { + Channel string `json:"channel" binding:"required"` + UUID string `json:"uuid" binding:"required"` + State map[string][]byte `json:"state"` + Close bool `json:"close"` +} + +func newChannelData(name string) (cd *channelData) { + cd = new(channelData) + cd.Name = name + cd.State = make(map[string][]byte) + cd.State["x"] = []byte{} + cd.State["curve"] = []byte{} + cd.State["y"] = []byte{} + cd.State["hh_k"] = []byte{} + cd.State["sender_ready"] = []byte{0} + cd.State["recipient_ready"] = []byte{0} + cd.State["is_open"] = []byte{0} + return +} diff --git a/src/server.go b/src/server.go new file mode 100644 index 0000000..3b66044 --- /dev/null +++ b/src/server.go @@ -0,0 +1,194 @@ +package croc + +import ( + "bytes" + "crypto/elliptic" + "encoding/json" + "fmt" + "sync" + "time" + + log "github.com/cihub/seelog" + "github.com/frankenbeanies/uuid4" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +type relayState struct { + channel map[string]*channelData + sync.RWMutex +} + +var rs relayState + +func init() { + rs.Lock() + rs.channel = make(map[string]*channelData) + rs.Unlock() +} + +const ( + state_curve = "curve" + state_hh_k = "hh_k" + state_is_open = "is_open" + state_recipient_ready = "recipient_ready" + state_sender_ready = "sender_ready" + state_x = "x" + state_y = "y" +) + +func RunRelay(port string) (err error) { + gin.SetMode(gin.ReleaseMode) + r := gin.New() + r.Use(middleWareHandler(), gin.Recovery()) + r.POST("/channel", func(c *gin.Context) { + r, err := func(c *gin.Context) (r response, err error) { + rs.Lock() + defer rs.Unlock() + r.Success = true + var p payloadChannel + err = c.ShouldBindJSON(&p) + if err != nil { + log.Errorf("failed on payload %+v", p) + err = errors.Wrap(err, "problem parsing /channel") + return + } + + // determine if channel is invalid + if _, ok := rs.channel[p.Channel]; !ok { + err = errors.Errorf("channel '%s' does not exist", p.Channel) + return + } + + // determine if UUID is invalid for channel + if p.UUID != rs.channel[p.Channel].uuids[0] && + p.UUID != rs.channel[p.Channel].uuids[1] { + err = errors.Errorf("uuid '%s' is invalid", p.UUID) + return + } + + // check if the action is to close the channel + if p.Close { + delete(rs.channel, p.Channel) + r.Message = "deleted " + p.Channel + return + } + + // assign each key provided + assignedKeys := []string{} + for key := range p.State { + // TODO: + // add a check that the value of key is not enormous + + // add only if it is a valid key + if _, ok := rs.channel[p.Channel].State[key]; ok { + assignedKeys = append(assignedKeys, key) + rs.channel[p.Channel].State[key] = p.State[key] + } + } + + // return the current state + r.State = make(map[string][]byte) + for key := range rs.channel[p.Channel].State { + r.State[key] = rs.channel[p.Channel].State[key] + } + + r.Message = fmt.Sprintf("assigned %d keys: %v", len(assignedKeys), assignedKeys) + return + }(c) + if err != nil { + r.Message = err.Error() + r.Success = false + } + bR, _ := json.Marshal(r) + c.Data(200, "application/json", bR) + }) + r.POST("/join", func(c *gin.Context) { + r, err := func(c *gin.Context) (r response, err error) { + rs.Lock() + defer rs.Unlock() + r.Success = true + + var p payloadOpen + err = c.ShouldBindJSON(&p) + if err != nil { + log.Errorf("failed on payload %+v", p) + err = errors.Wrap(err, "problem parsing") + return + } + + // determine if sender or recipient + if p.Role != 0 && p.Role != 1 { + err = errors.Errorf("no such role of %d", p.Role) + return + } + + // determine channel + if p.Channel == "" { + // TODO: + // find an empty channel + p.Channel = "chou" + } + if _, ok := rs.channel[p.Channel]; ok { + // channel is not empty + if rs.channel[p.Channel].uuids[p.Role] != "" { + err = errors.Errorf("channel is already occupied by role %d", p.Role) + return + } + } + r.Channel = p.Channel + if _, ok := rs.channel[r.Channel]; !ok { + rs.channel[r.Channel] = newChannelData(r.Channel) + } + + // assign UUID for the role in the channel + rs.channel[r.Channel].uuids[p.Role] = uuid4.New().String() + r.UUID = rs.channel[r.Channel].uuids[p.Role] + + // if channel is not open, determine curve + if bytes.Equal(rs.channel[r.Channel].State[state_is_open], []byte{0}) { + switch curve := p.Curve; curve { + case "p224": + rs.channel[r.Channel].curve = elliptic.P224() + case "p256": + rs.channel[r.Channel].curve = elliptic.P256() + case "p384": + rs.channel[r.Channel].curve = elliptic.P384() + case "p521": + rs.channel[r.Channel].curve = elliptic.P521() + default: + // TODO: + // add SIEC + p.Curve = "p256" + rs.channel[r.Channel].curve = elliptic.P256() + } + rs.channel[r.Channel].State[state_curve] = []byte(p.Curve) + rs.channel[r.Channel].State[state_is_open] = []byte{1} + } + + r.Message = fmt.Sprintf("assigned role %d in channel '%s'", p.Role, r.Channel) + return + }(c) + if err != nil { + r.Message = err.Error() + r.Success = false + } + bR, _ := json.Marshal(r) + c.Data(200, "application/json", bR) + }) + log.Infof("Running at http://0.0.0.0:" + port) + err = r.Run(":" + port) + return +} + +func middleWareHandler() gin.HandlerFunc { + return func(c *gin.Context) { + t := time.Now() + // Add base headers + // addCORS(c) + // Run next function + c.Next() + // Log request + log.Infof("%v %v %v %s", c.Request.RemoteAddr, c.Request.Method, c.Request.URL, time.Since(t)) + } +}