mirror of
https://github.com/schollz/croc.git
synced 2025-10-11 21:30:16 +02:00
simplify setup
This commit is contained in:
parent
7d07ccfe40
commit
f64427f70d
6 changed files with 78 additions and 84 deletions
13
README.md
13
README.md
|
@ -100,12 +100,13 @@ croc.Receive()
|
||||||
- Generates X from pw.
|
- Generates X from pw.
|
||||||
- Sender sends X to relay.
|
- Sender sends X to relay.
|
||||||
|
|
||||||
*Is Y available?*
|
*Is Y and Bcrypt(k_B) available?*
|
||||||
|
|
||||||
- Use *Y* to generate its session key *k_A* and *H(k_A)*, and checks *H(H(k_A))*==*H(H(k_B))*. Abort here if it is incorrect.
|
- Use *Y* to generate its session key *k_A*.
|
||||||
|
- Check that Bcrypt(k_B) comes from k_A. Abort here if it is incorrect.
|
||||||
- Encrypts data using *k_A*.
|
- Encrypts data using *k_A*.
|
||||||
- Connect to TCP ports of Relay.
|
- Connect to TCP ports of Relay.
|
||||||
- Send the Relay authentication *H(k_A)*.
|
- Send the Relay authentication *Bcrypt(k_A)*.
|
||||||
|
|
||||||
*Are ports stapled?*
|
*Are ports stapled?*
|
||||||
|
|
||||||
|
@ -121,11 +122,11 @@ croc.Receive()
|
||||||
*Is X available?*
|
*Is X available?*
|
||||||
|
|
||||||
- Generate *Y*, session key *k_B*, and hashed session key *H(k_B)* using PAKE from secret *pw*.
|
- Generate *Y*, session key *k_B*, and hashed session key *H(k_B)* using PAKE from secret *pw*.
|
||||||
- Send the Relay *H(H(k_B))*
|
- Send the Relay *Bcrypt(k_B)*
|
||||||
|
|
||||||
*Is H(k_A) available?*
|
*Is Bcrypt(k_A) available?*
|
||||||
|
|
||||||
- Verify that *H(k_A)* equals *H(k_B)*
|
- Verify that *Bcrypt(k_A)* comes from k_B
|
||||||
- Connect to TCP ports of Relay and listen.
|
- Connect to TCP ports of Relay and listen.
|
||||||
- Once file is received, Send close signal to Relay.
|
- Once file is received, Send close signal to Relay.
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,15 @@ import (
|
||||||
|
|
||||||
func (c *Croc) client(role int) (err error) {
|
func (c *Croc) client(role int) (err error) {
|
||||||
defer log.Flush()
|
defer log.Flush()
|
||||||
|
codePhrase := "chou"
|
||||||
|
|
||||||
// initialize the channel data for this client
|
// initialize the channel data for this client
|
||||||
c.cs.Lock()
|
c.cs.Lock()
|
||||||
c.cs.channel = newChannelData("")
|
c.cs.channel = newChannelData("")
|
||||||
|
c.cs.channel.codePhrase = codePhrase
|
||||||
|
c.cs.channel.Channel = codePhrase[:3]
|
||||||
|
channel := codePhrase[:3]
|
||||||
|
c.cs.channel.secret["pw"] = []byte(codePhrase[3:])
|
||||||
c.cs.Unlock()
|
c.cs.Unlock()
|
||||||
|
|
||||||
interrupt := make(chan os.Signal, 1)
|
interrupt := make(chan os.Signal, 1)
|
||||||
|
@ -45,7 +50,7 @@ func (c *Croc) client(role int) (err error) {
|
||||||
log.Debugf("sender read error:", err)
|
log.Debugf("sender read error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debugf("recv: %+v", cd)
|
log.Debugf("recv: %s", cd)
|
||||||
err = c.processState(cd)
|
err = c.processState(cd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
|
@ -57,10 +62,13 @@ func (c *Croc) client(role int) (err error) {
|
||||||
// initialize by joining as corresponding role
|
// initialize by joining as corresponding role
|
||||||
// TODO:
|
// TODO:
|
||||||
// allowing suggesting a channel
|
// allowing suggesting a channel
|
||||||
err = ws.WriteJSON(payload{
|
p := payload{
|
||||||
Open: true,
|
Open: true,
|
||||||
Role: role,
|
Role: role,
|
||||||
})
|
Channel: channel,
|
||||||
|
}
|
||||||
|
log.Debugf("sending opening payload: %+v", p)
|
||||||
|
err = ws.WriteJSON(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("problem opening: %s", err.Error())
|
log.Errorf("problem opening: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
@ -116,16 +124,16 @@ func (c *Croc) processState(cd channelData) (err error) {
|
||||||
// initialize if has UUID
|
// initialize if has UUID
|
||||||
if cd.UUID != "" {
|
if cd.UUID != "" {
|
||||||
c.cs.channel.UUID = cd.UUID
|
c.cs.channel.UUID = cd.UUID
|
||||||
c.cs.channel.Ports = cd.Ports
|
|
||||||
c.cs.channel.Channel = cd.Channel
|
c.cs.channel.Channel = cd.Channel
|
||||||
c.cs.channel.Role = cd.Role
|
c.cs.channel.Role = cd.Role
|
||||||
c.cs.channel.Ports = cd.Ports
|
|
||||||
log.Debugf("initialized client state")
|
log.Debugf("initialized client state")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// copy over the rest of the state
|
// copy over the rest of the state
|
||||||
if cd.TransferReady {
|
if cd.TransferReady {
|
||||||
c.cs.channel.TransferReady = true
|
c.cs.channel.TransferReady = true
|
||||||
}
|
}
|
||||||
|
c.cs.channel.Ports = cd.Ports
|
||||||
for key := range cd.State {
|
for key := range cd.State {
|
||||||
c.cs.channel.State[key] = cd.State[key]
|
c.cs.channel.State[key] = cd.State[key]
|
||||||
}
|
}
|
||||||
|
@ -134,6 +142,6 @@ func (c *Croc) processState(cd channelData) (err error) {
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// process the client state
|
// process the client state
|
||||||
log.Debugf("processing client state: %+v", c.cs.channel)
|
log.Debugf("processing client state: %+v", c.cs.channel.String2())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
mathrand "math/rand"
|
mathrand "math/rand"
|
||||||
|
@ -27,7 +26,7 @@ func init() {
|
||||||
func getRandomName() string {
|
func getRandomName() string {
|
||||||
result := []string{}
|
result := []string{}
|
||||||
bs := make([]byte, 4)
|
bs := make([]byte, 4)
|
||||||
binary.LittleEndian.PutUint32(bs, mathrand.Uint32())
|
rand.Read(bs)
|
||||||
result = mnemonicode.EncodeWordList(result, bs)
|
result = mnemonicode.EncodeWordList(result, bs)
|
||||||
return strings.Join(result, "-")
|
return strings.Join(result, "-")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package croc
|
package croc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,9 +17,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// see PAKE setup for more info: https://play.golang.org/p/QLHvINK4qFG
|
// see PAKE setup for more info: https://play.golang.org/p/Sd0eTuuEIWu
|
||||||
// availableStates are the varaibles available to the parties involved
|
// availableStates are the varaibles available to the parties involved
|
||||||
availableStates = []string{"curve", "Xᵤ", "Xᵥ", "Yᵤ", "Yᵥ", "Uᵤ", "Uᵥ", "Vᵤ", "Vᵥ", "HHBk", "HAk"}
|
availableStates = []string{"curve", "Xᵤ", "Xᵥ", "Yᵤ", "Yᵥ", "Uᵤ", "Uᵥ", "Vᵤ", "Vᵥ", "Bcrypt(Ak)", "Bcrypt(Bk)"}
|
||||||
// availableSecrets are the variables available only to a specific client, and not shared
|
// availableSecrets are the variables available only to a specific client, and not shared
|
||||||
availableSecrets = []string{"pw", "Upwᵤ", "Upwᵥ", "α", "αᵤ", "αᵥ", "Vpwᵤ", "Vpwᵥ", "β", "gβᵤ", "gβᵥ", "BZᵤ", "BZᵥ", "BZᵤ", "BZᵥ", "AZᵤ", "AZᵥ", "AZᵤ", "AZᵥ", "Bk", "Ak"}
|
availableSecrets = []string{"pw", "Upwᵤ", "Upwᵥ", "α", "αᵤ", "αᵥ", "Vpwᵤ", "Vpwᵥ", "β", "gβᵤ", "gβᵥ", "BZᵤ", "BZᵥ", "BZᵤ", "BZᵥ", "AZᵤ", "AZᵥ", "AZᵤ", "AZᵥ", "Bk", "Ak"}
|
||||||
)
|
)
|
||||||
|
@ -89,12 +91,12 @@ type channelData struct {
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
// client parameters
|
// client parameters
|
||||||
|
// codePhrase uses the first 3 characters to establish a channel, and the rest
|
||||||
|
// to form the passphrase
|
||||||
|
codePhrase string
|
||||||
// secret are the computed secretes
|
// secret are the computed secretes
|
||||||
// contains "curve", "h_k", "hh_k", "x", "y"
|
// contains "curve", "h_k", "hh_k", "x", "y"
|
||||||
secret map[string][]byte `json:"secret"`
|
secret map[string][]byte
|
||||||
|
|
||||||
// relay + client parameters
|
|
||||||
// curve is the type of elliptic curve used for PAKE
|
// curve is the type of elliptic curve used for PAKE
|
||||||
curve elliptic.Curve
|
curve elliptic.Curve
|
||||||
|
|
||||||
|
@ -111,15 +113,20 @@ type channelData struct {
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type response struct {
|
func (cd channelData) String2() string {
|
||||||
// various responses
|
for key := range cd.State {
|
||||||
Channel string `json:"channel,omitempty"`
|
if bytes.Equal(cd.State[key], []byte{}) {
|
||||||
UUID string `json:"uuid,omitempty"`
|
delete(cd.State, key)
|
||||||
Data *channelData `json:"data,omitempty"`
|
}
|
||||||
|
}
|
||||||
|
for key := range cd.secret {
|
||||||
|
if !bytes.Equal(cd.secret[key], []byte{}) {
|
||||||
|
cd.State[key] = cd.secret[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cdb, _ := json.Marshal(cd)
|
||||||
|
|
||||||
// constant responses
|
return string(cdb)
|
||||||
Success bool `json:"success"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type payload struct {
|
type payload struct {
|
||||||
|
|
|
@ -32,15 +32,10 @@ func (c *Croc) startServer(tcpPorts []string, port string) (err error) {
|
||||||
if _, ok := err.(*websocket.CloseError); ok {
|
if _, ok := err.(*websocket.CloseError); ok {
|
||||||
// on forced close, delete the channel
|
// on forced close, delete the channel
|
||||||
log.Debug("closed channel")
|
log.Debug("closed channel")
|
||||||
c.rs.Lock()
|
c.closeChannel(channel)
|
||||||
if _, ok := c.rs.channel[channel]; ok {
|
|
||||||
delete(c.rs.channel, channel)
|
|
||||||
}
|
|
||||||
c.rs.Unlock()
|
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("read:", err)
|
log.Debugf("read:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
channel, err = c.processPayload(ws, p)
|
channel, err = c.processPayload(ws, p)
|
||||||
|
@ -48,9 +43,7 @@ func (c *Croc) startServer(tcpPorts []string, port string) (err error) {
|
||||||
// if error, send the error back and then delete the channel
|
// if error, send the error back and then delete the channel
|
||||||
log.Warn("problem processing payload %+v: %s", p, err.Error())
|
log.Warn("problem processing payload %+v: %s", p, err.Error())
|
||||||
ws.WriteJSON(channelData{Error: err.Error()})
|
ws.WriteJSON(channelData{Error: err.Error()})
|
||||||
c.rs.Lock()
|
c.closeChannel(channel)
|
||||||
delete(c.rs.channel, p.Channel)
|
|
||||||
c.rs.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,6 +88,7 @@ func (c *Croc) updateChannel(p payload) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Croc) joinChannel(ws *websocket.Conn, p payload) (channel string, err error) {
|
func (c *Croc) joinChannel(ws *websocket.Conn, p payload) (channel string, err error) {
|
||||||
|
log.Debugf("joining channel %s", ws.RemoteAddr().String())
|
||||||
c.rs.Lock()
|
c.rs.Lock()
|
||||||
defer c.rs.Unlock()
|
defer c.rs.Unlock()
|
||||||
|
|
||||||
|
@ -117,6 +111,7 @@ func (c *Croc) joinChannel(ws *websocket.Conn, p payload) (channel string, err e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log.Debug("creating new channel")
|
||||||
if _, ok := c.rs.channel[p.Channel]; !ok {
|
if _, ok := c.rs.channel[p.Channel]; !ok {
|
||||||
c.rs.channel[p.Channel] = newChannelData(p.Channel)
|
c.rs.channel[p.Channel] = newChannelData(p.Channel)
|
||||||
}
|
}
|
||||||
|
@ -140,7 +135,7 @@ func (c *Croc) joinChannel(ws *websocket.Conn, p payload) (channel string, err e
|
||||||
c.rs.channel[p.Channel].isopen = true
|
c.rs.channel[p.Channel].isopen = true
|
||||||
c.rs.channel[p.Channel].Ports = c.TcpPorts
|
c.rs.channel[p.Channel].Ports = c.TcpPorts
|
||||||
c.rs.channel[p.Channel].startTime = time.Now()
|
c.rs.channel[p.Channel].startTime = time.Now()
|
||||||
p.Curve, c.rs.channel[p.Channel].curve = getCurve(p.Curve)
|
p.Curve, _ = getCurve(p.Curve)
|
||||||
log.Debugf("(%s) using curve '%s'", p.Channel, p.Curve)
|
log.Debugf("(%s) using curve '%s'", p.Channel, p.Curve)
|
||||||
c.rs.channel[p.Channel].State["curve"] = []byte(p.Curve)
|
c.rs.channel[p.Channel].State["curve"] = []byte(p.Curve)
|
||||||
}
|
}
|
||||||
|
@ -150,15 +145,32 @@ func (c *Croc) joinChannel(ws *websocket.Conn, p payload) (channel string, err e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// closeChannel will shut down current open websockets and delete the channel information
|
||||||
|
func (c *Croc) closeChannel(channel string) {
|
||||||
|
c.rs.Lock()
|
||||||
|
defer c.rs.Unlock()
|
||||||
|
// check if channel exists
|
||||||
|
if _, ok := c.rs.channel[channel]; !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// close open connections
|
||||||
|
for _, wsConn := range c.rs.channel[channel].websocketConn {
|
||||||
|
if wsConn != nil {
|
||||||
|
wsConn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// delete
|
||||||
|
delete(c.rs.channel, channel)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Croc) processPayload(ws *websocket.Conn, p payload) (channel string, err error) {
|
func (c *Croc) processPayload(ws *websocket.Conn, p payload) (channel string, err error) {
|
||||||
|
log.Debugf("processing payload from %s", ws.RemoteAddr().String())
|
||||||
channel = p.Channel
|
channel = p.Channel
|
||||||
|
|
||||||
// if the request is to close, delete the channel
|
// if the request is to close, delete the channel
|
||||||
if p.Close {
|
if p.Close {
|
||||||
log.Debugf("closing channel %s", p.Channel)
|
log.Debugf("closing channel %s", p.Channel)
|
||||||
c.rs.Lock()
|
c.closeChannel(p.Channel)
|
||||||
delete(c.rs.channel, p.Channel)
|
|
||||||
c.rs.Unlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +212,7 @@ func (c *Croc) processPayload(ws *websocket.Conn, p payload) (channel string, er
|
||||||
if wsConn == nil {
|
if wsConn == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Debugf("writing latest data %+v to %d", c.rs.channel[channel], role)
|
log.Debugf("writing latest data %+v to %d", c.rs.channel[channel].String2(), role)
|
||||||
err = wsConn.WriteJSON(c.rs.channel[channel])
|
err = wsConn.WriteJSON(c.rs.channel[channel])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("problem writing to role %d: %s", role, err.Error())
|
log.Debugf("problem writing to role %d: %s", role, err.Error())
|
||||||
|
@ -221,13 +233,18 @@ func (c *Croc) channelCleanup() {
|
||||||
keys[i] = key
|
keys[i] = key
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
channelsToDelete := []string{}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
if time.Since(c.rs.channel[key].startTime) > maximumWait {
|
if time.Since(c.rs.channel[key].startTime) > maximumWait {
|
||||||
log.Debugf("channel %s has exceeded time, deleting", key)
|
channelsToDelete = append(channelsToDelete, key)
|
||||||
delete(c.rs.channel, key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.rs.Unlock()
|
c.rs.Unlock()
|
||||||
|
|
||||||
|
for _, channel := range channelsToDelete {
|
||||||
|
log.Debugf("channel %s has exceeded time, deleting", channel)
|
||||||
|
c.closeChannel(channel)
|
||||||
|
}
|
||||||
time.Sleep(1 * time.Minute)
|
time.Sleep(1 * time.Minute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
wiped from the relay server. The encrypted file data never is stored on the relay.
|
|
||||||
|
|
||||||
**Encryption**
|
|
||||||
|
|
||||||
Encryption uses AES-256 with a pbkdf2 derived key (see [RFC2898](http://www.ietf.org/rfc/rfc2898.txt)) where the code phrase shared between the sender and receiver is used as the passphrase. For each of the two encrypted data blocks (metadata stored on relay server, and file data transmitted), a random 8-byte salt is used and a IV is generated according to [NIST Recommendation for Block ciphers, Section 8.2](http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf).
|
|
||||||
|
|
||||||
**Decryption**
|
|
||||||
|
|
||||||
On the receiver's computer, each piece of received encrypted data is written to a separate file. These files are concatenated and then decrypted. The hash of the decrypted file is then checked against the hash transmitted from the sender (part of the meta data block).
|
|
||||||
|
|
||||||
## Run your own relay
|
|
||||||
|
|
||||||
*croc* relies on a TCP relay to staple the parallel incoming and outgoing connections. The relay temporarily stores connection information and the encrypted meta information. The default uses a public relay at, `cowyo.com`, which has a 30-day uptime of 99.989% ([click here to check the current status of the public relay](https://stats.uptimerobot.com/lOwJYIgRm)).
|
|
||||||
|
|
||||||
You can also run your own relay, it is very easy. On your server, `your-server.com`, just run
|
|
||||||
|
|
||||||
```
|
|
||||||
$ croc -relay
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, when you use *croc* to send and receive you should add `-server your-server.com` to use your relay server. Make sure to open up TCP ports 27001-27009.
|
|
||||||
|
|
||||||
# Contribute
|
|
||||||
|
|
||||||
I am awed by all the [great contributions](#acknowledgements) made! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)).
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
||||||
# Acknowledgements
|
|
||||||
|
|
||||||
Thanks...
|
|
||||||
|
|
||||||
- ...[@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole).
|
|
||||||
- ...[@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28).
|
|
||||||
- ...[@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/).
|
|
||||||
- ...for making pull requests [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu)!
|
|
Loading…
Add table
Add a link
Reference in a new issue