first attempt

This commit is contained in:
Zack Scholl 2018-06-28 06:38:07 -07:00
parent 84f732b662
commit 5ede3186e5
5 changed files with 318 additions and 120 deletions

140
README.md
View File

@ -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

10
main.go Normal file
View File

@ -0,0 +1,10 @@
package main
import croc "github.com/schollz/croc/src"
func main() {
err := croc.RunRelay("8002")
if err != nil {
panic(err)
}
}

43
src/logging.go Normal file
View File

@ -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 := `
<seelog minlevel="` + level + `">
<outputs formatid="stdout">
<filter levels="debug,trace">
<console formatid="debug"/>
</filter>
<filter levels="info">
<console formatid="info"/>
</filter>
<filter levels="critical,error">
<console formatid="error"/>
</filter>
<filter levels="warn">
<console formatid="warn"/>
</filter>
</outputs>
<formats>
<format id="stdout" format="%Date %Time [%LEVEL] %File %FuncShort:%Line %Msg %n" />
<format id="debug" format="%Date %Time %EscM(37)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
<format id="info" format="%Date %Time %EscM(36)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
<format id="warn" format="%Date %Time %EscM(33)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
<format id="error" format="%Date %Time %EscM(31)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
</formats>
</seelog>
`
logger, err := log.LoggerFromConfigAsBytes([]byte(appConfig))
if err != nil {
return
}
log.ReplaceLogger(logger)
return
}

View File

@ -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
}

194
src/server.go Normal file
View File

@ -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))
}
}