mirror of https://github.com/schollz/croc.git
first attempt
This commit is contained in:
parent
84f732b662
commit
5ede3186e5
140
README.md
140
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
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import croc "github.com/schollz/croc/src"
|
||||
|
||||
func main() {
|
||||
err := croc.RunRelay("8002")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue