mirror of
https://github.com/schollz/croc.git
synced 2025-10-11 21:30:16 +02:00
commit
8d43a1f63a
15 changed files with 628 additions and 111 deletions
2
.github/default.txt
vendored
2
.github/default.txt
vendored
|
@ -33,7 +33,7 @@ install_croc()
|
||||||
croc_os="unsupported"
|
croc_os="unsupported"
|
||||||
croc_arch="unknown"
|
croc_arch="unknown"
|
||||||
croc_arm=""
|
croc_arm=""
|
||||||
croc_version="4.0.8"
|
croc_version="4.1.0"
|
||||||
|
|
||||||
|
|
||||||
# Termux on Android has $PREFIX set which already ends with /usr
|
# Termux on Android has $PREFIX set which already ends with /usr
|
||||||
|
|
17
.github/issue_template.md
vendored
Normal file
17
.github/issue_template.md
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
## Expected Behavior
|
||||||
|
|
||||||
|
|
||||||
|
## Actual Behavior
|
||||||
|
|
||||||
|
|
||||||
|
## Steps to Reproduce the Problem
|
||||||
|
|
||||||
|
1.
|
||||||
|
1.
|
||||||
|
1.
|
||||||
|
|
||||||
|
## Specifications
|
||||||
|
|
||||||
|
- Version (`croc -version` or let me know latest commit in git log if using dev):
|
||||||
|
- Platform:
|
||||||
|
- Subsystem:
|
|
@ -1,7 +1,7 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- tip
|
- "1.11"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- "PATH=/home/travis/gopath/bin:$PATH"
|
- "PATH=/home/travis/gopath/bin:$PATH"
|
||||||
|
|
23
README.md
23
README.md
|
@ -4,7 +4,7 @@
|
||||||
src="https://user-images.githubusercontent.com/6550035/46709024-9b23ad00-cbf6-11e8-9fb2-ca8b20b7dbec.jpg"
|
src="https://user-images.githubusercontent.com/6550035/46709024-9b23ad00-cbf6-11e8-9fb2-ca8b20b7dbec.jpg"
|
||||||
width="408px" border="0" alt="croc">
|
width="408px" border="0" alt="croc">
|
||||||
<br>
|
<br>
|
||||||
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-4.0.8-brightgreen.svg?style=flat-square" alt="Version"></a>
|
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-4.1.0-brightgreen.svg?style=flat-square" alt="Version"></a>
|
||||||
<img src="https://img.shields.io/badge/coverage-77%25-brightgreen.svg?style=flat-square" alt="Code coverage">
|
<img src="https://img.shields.io/badge/coverage-77%25-brightgreen.svg?style=flat-square" alt="Code coverage">
|
||||||
<a href="https://travis-ci.org/schollz/croc"><img
|
<a href="https://travis-ci.org/schollz/croc"><img
|
||||||
src="https://img.shields.io/travis-ci/schollz/croc.svg?style=flat-square" alt="Build
|
src="https://img.shields.io/travis-ci/schollz/croc.svg?style=flat-square" alt="Build
|
||||||
|
@ -19,11 +19,17 @@ Status"></a>
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
*croc* uses "code phrases" to securely transfer files. A code phrase is a combination of three random words (mnemonicoded 4 bytes) which the sender shares with the recipient. The code phrase is used by the sender and recipient for password authenticated key exchange ([PAKE](https://github.com/schollz/pake)) to validate parties and generate a secure session key for end-to-end encryption. Since a code phrase can only be used once between two parties, an attacker has a chance of less than 1 in *4 billion* to guess the right code phrase to steal the file. Any attacker with the wrong code phrase will fail the PAKE and the sender will be notified. Only two people with the right code phrase will be able to computers transfer encrypted data through a relay.
|
**Transmit encrypted data with a code phrase**
|
||||||
|
|
||||||
|
*croc* securely transfers data using *code phrases* - a combination of three random words (mnemonicoded 4 bytes). The code phrase is shared between the sender and the recipient for password authenticated key exchange ([PAKE](https://github.com/schollz/pake)), a cryptographic method to use a shared weak key (the "code phrase") to generate a strong key for secure end-to-end encryption. By default, a code phrase can only be used once between two parties so an attacker would have a chance of less than 1 in *4 billion* to guess the code phrase correctly to steal the data. An attacker with the wrong code phrase will fail the PAKE and the sender will be notified without any data transfering. Only two people with the right code phrase will be able to computers transfer encrypted data through a relay.
|
||||||
|
|
||||||
|
**Fast data transfer through TCP**
|
||||||
|
|
||||||
The actual data transfer is accomplished using a relay, either using raw TCP sockets or websockets. If both computers are on the LAN network then *croc* will use a local relay, otherwise a public relay is used. All the data going through the relay is encrypted using the PAKE-generated session key, so the relay can't spy on information passing through it. The data is transferred in blocks, where each block is compressed and encrypted, and the recipient keeps track of blocks received so that it can resume the transfer if interrupted.
|
The actual data transfer is accomplished using a relay, either using raw TCP sockets or websockets. If both computers are on the LAN network then *croc* will use a local relay, otherwise a public relay is used. All the data going through the relay is encrypted using the PAKE-generated session key, so the relay can't spy on information passing through it. The data is transferred in blocks, where each block is compressed and encrypted, and the recipient keeps track of blocks received so that it can resume the transfer if interrupted.
|
||||||
|
|
||||||
My motivation to write *croc*, as stupid as it sounds, is because I wanted to create a program that made it easy to send a 3GB+ PBS documentary to my friend in a different country. My friend has a Windows computer and is not comfortable using a terminal. So I wanted to write a program that, while secure, is simple to receive a file. *croc* accomplishes this, and now I find myself using it almost everyday at work. To receive a file you can just download the executable and double click on it (sending a file requires opening a terminal still, though).
|
**Why another data transfer utility?**
|
||||||
|
|
||||||
|
My motivation to write *croc*, as stupid as it sounds, is because I wanted to create a program that made it easy to send a 3GB+ PBS documentary to my friend in a different country. My friend has a Windows computer and is not comfortable using a terminal. So I wanted to write a program that, while secure, is simple to receive a file. *croc* accomplishes this, and now I find myself using it almost everyday at work. To receive a file you can just download the executable and double click on it (sending a file requires opening a terminal still, though). The name is inspired by the [fable of the frog and the crocodile](https://web.archive.org/web/20180926035731/http://allaboutfrogs.org/stories/crocodile.html).
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -128,6 +134,17 @@ You can send files using your relay by entering `-relay` to change the relay tha
|
||||||
$ croc -relay "ws://myrelay.example.com" send [filename]
|
$ croc -relay "ws://myrelay.example.com" send [filename]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Configuration file
|
||||||
|
|
||||||
|
You can also make some paramters static by using a configuration file. To get started with the config file just do
|
||||||
|
|
||||||
|
```
|
||||||
|
$ croc config
|
||||||
|
```
|
||||||
|
|
||||||
|
which will generate the file that you can edit.
|
||||||
|
Any changes you make to the configuration file will be applied *before* the command-line flags, if any.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
13
go.mod
13
go.mod
|
@ -1,24 +1,23 @@
|
||||||
module github.com/schollz/croc
|
module github.com/schollz/croc
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/BurntSushi/toml v0.3.1
|
||||||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575
|
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575
|
||||||
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/fatih/color v1.7.0 // indirect
|
github.com/fatih/color v1.7.0 // indirect
|
||||||
github.com/gorilla/websocket v1.4.0
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/mars9/crypt v0.0.0-20150406101210-65899cf653ff // indirect
|
|
||||||
github.com/mattn/go-colorable v0.0.9 // indirect
|
github.com/mattn/go-colorable v0.0.9 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.4 // indirect
|
github.com/mattn/go-isatty v0.0.4 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.0.0
|
||||||
github.com/pkg/errors v0.8.0
|
github.com/pkg/errors v0.8.0
|
||||||
github.com/schollz/mnemonicode v1.0.1
|
github.com/schollz/mnemonicode v1.0.1
|
||||||
github.com/schollz/pake v1.0.2
|
github.com/schollz/pake v1.1.0
|
||||||
github.com/schollz/peerdiscovery v1.2.2
|
github.com/schollz/peerdiscovery v1.2.2
|
||||||
github.com/schollz/progressbar/v2 v2.6.0
|
github.com/schollz/progressbar/v2 v2.6.0
|
||||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9
|
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9
|
||||||
github.com/schollz/utils v1.0.0
|
|
||||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c
|
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/stretchr/testify v1.2.2
|
||||||
github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937
|
|
||||||
github.com/urfave/cli v1.20.0
|
github.com/urfave/cli v1.20.0
|
||||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
|
||||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 // indirect
|
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f // indirect
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -19,10 +20,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var Version string
|
var Version string
|
||||||
var codePhrase string
|
|
||||||
var cr *croc.Croc
|
var cr *croc.Croc
|
||||||
|
|
||||||
func Run() {
|
func Run() {
|
||||||
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "croc"
|
app.Name = "croc"
|
||||||
if Version == "" {
|
if Version == "" {
|
||||||
|
@ -60,6 +61,16 @@ func Run() {
|
||||||
return relay(c)
|
return relay(c)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "config",
|
||||||
|
Usage: "generates a config file",
|
||||||
|
Description: "the croc config can be used to set static parameters",
|
||||||
|
Flags: []cli.Flag{},
|
||||||
|
HelpName: "croc config",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return saveDefaultConfig(c)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
app.Flags = []cli.Flag{
|
app.Flags = []cli.Flag{
|
||||||
cli.StringFlag{Name: "addr", Value: "croc4.schollz.com", Usage: "address of the public relay"},
|
cli.StringFlag{Name: "addr", Value: "croc4.schollz.com", Usage: "address of the public relay"},
|
||||||
|
@ -74,7 +85,8 @@ func Run() {
|
||||||
cli.BoolFlag{Name: "force-web", Usage: "force websockets"},
|
cli.BoolFlag{Name: "force-web", Usage: "force websockets"},
|
||||||
cli.StringFlag{Name: "port", Value: "8153", Usage: "port that the websocket listens on"},
|
cli.StringFlag{Name: "port", Value: "8153", Usage: "port that the websocket listens on"},
|
||||||
cli.StringFlag{Name: "tcp-port", Value: "8154,8155,8156,8157,8158,8159,8160,8161", Usage: "ports that the tcp server listens on"},
|
cli.StringFlag{Name: "tcp-port", Value: "8154,8155,8156,8157,8158,8159,8160,8161", Usage: "ports that the tcp server listens on"},
|
||||||
cli.StringFlag{Name: "curve", Value: "siec", Usage: "specify elliptic curve to use (p224, p256, p384, p521, siec)"},
|
cli.StringFlag{Name: "curve", Value: "siec", Usage: "specify elliptic curve to use for PAKE (p256, p384, p521, siec)"},
|
||||||
|
cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
|
||||||
}
|
}
|
||||||
app.EnableBashCompletion = true
|
app.EnableBashCompletion = true
|
||||||
app.HideHelp = false
|
app.HideHelp = false
|
||||||
|
@ -87,6 +99,7 @@ func Run() {
|
||||||
}
|
}
|
||||||
app.Before = func(c *cli.Context) error {
|
app.Before = func(c *cli.Context) error {
|
||||||
cr = croc.Init(c.GlobalBool("debug"))
|
cr = croc.Init(c.GlobalBool("debug"))
|
||||||
|
cr.Version = Version
|
||||||
cr.AllowLocalDiscovery = true
|
cr.AllowLocalDiscovery = true
|
||||||
cr.Address = c.GlobalString("addr")
|
cr.Address = c.GlobalString("addr")
|
||||||
cr.AddressTCPPorts = strings.Split(c.GlobalString("addr-tcp"), ",")
|
cr.AddressTCPPorts = strings.Split(c.GlobalString("addr-tcp"), ",")
|
||||||
|
@ -114,6 +127,10 @@ func Run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveDefaultConfig(c *cli.Context) error {
|
||||||
|
return croc.SaveDefaultConfig()
|
||||||
|
}
|
||||||
|
|
||||||
func send(c *cli.Context) error {
|
func send(c *cli.Context) error {
|
||||||
stat, _ := os.Stdin.Stat()
|
stat, _ := os.Stdin.Stat()
|
||||||
var fname string
|
var fname string
|
||||||
|
@ -146,11 +163,12 @@ func send(c *cli.Context) error {
|
||||||
cr.UseCompression = !c.Bool("no-compress")
|
cr.UseCompression = !c.Bool("no-compress")
|
||||||
cr.UseEncryption = !c.Bool("no-encrypt")
|
cr.UseEncryption = !c.Bool("no-encrypt")
|
||||||
if c.String("code") != "" {
|
if c.String("code") != "" {
|
||||||
codePhrase = c.String("code")
|
cr.Codephrase = c.String("code")
|
||||||
}
|
}
|
||||||
if len(codePhrase) == 0 {
|
cr.LoadConfig()
|
||||||
|
if len(cr.Codephrase) == 0 {
|
||||||
// generate code phrase
|
// generate code phrase
|
||||||
codePhrase = utils.GetRandomName()
|
cr.Codephrase = utils.GetRandomName()
|
||||||
}
|
}
|
||||||
|
|
||||||
// print the text
|
// print the text
|
||||||
|
@ -175,28 +193,32 @@ func send(c *cli.Context) error {
|
||||||
humanize.Bytes(uint64(fsize)),
|
humanize.Bytes(uint64(fsize)),
|
||||||
fileOrFolder,
|
fileOrFolder,
|
||||||
filename,
|
filename,
|
||||||
codePhrase,
|
cr.Codephrase,
|
||||||
codePhrase,
|
cr.Codephrase,
|
||||||
)
|
)
|
||||||
return cr.Send(fname, codePhrase)
|
return cr.Send(fname, cr.Codephrase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func receive(c *cli.Context) error {
|
func receive(c *cli.Context) error {
|
||||||
if c.GlobalString("code") != "" {
|
if c.GlobalString("code") != "" {
|
||||||
codePhrase = c.GlobalString("code")
|
cr.Codephrase = c.GlobalString("code")
|
||||||
}
|
}
|
||||||
if c.Args().First() != "" {
|
if c.Args().First() != "" {
|
||||||
codePhrase = c.Args().First()
|
cr.Codephrase = c.Args().First()
|
||||||
}
|
}
|
||||||
|
if c.GlobalString("out") != "" {
|
||||||
|
os.Chdir(c.GlobalString("out"))
|
||||||
|
}
|
||||||
|
cr.LoadConfig()
|
||||||
openFolder := false
|
openFolder := false
|
||||||
if len(os.Args) == 1 {
|
if len(os.Args) == 1 {
|
||||||
// open folder since they didn't give any arguments
|
// open folder since they didn't give any arguments
|
||||||
openFolder = true
|
openFolder = true
|
||||||
}
|
}
|
||||||
if codePhrase == "" {
|
if cr.Codephrase == "" {
|
||||||
codePhrase = utils.GetInput("Enter receive code: ")
|
cr.Codephrase = utils.GetInput("Enter receive code: ")
|
||||||
}
|
}
|
||||||
err := cr.Receive(codePhrase)
|
err := cr.Receive(cr.Codephrase)
|
||||||
if err == nil && openFolder {
|
if err == nil && openFolder {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
open.Run(cwd)
|
open.Run(cwd)
|
||||||
|
|
|
@ -6,6 +6,13 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CompressWithOption returns compressed data using the specified level
|
||||||
|
func CompressWithOption(src []byte, level int) []byte {
|
||||||
|
compressedData := new(bytes.Buffer)
|
||||||
|
compress(src, compressedData, level)
|
||||||
|
return compressedData.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
// Compress returns a compressed byte slice.
|
// Compress returns a compressed byte slice.
|
||||||
func Compress(src []byte) []byte {
|
func Compress(src []byte) []byte {
|
||||||
compressedData := new(bytes.Buffer)
|
compressedData := new(bytes.Buffer)
|
||||||
|
|
78
src/compress/compress_test.go
Normal file
78
src/compress/compress_test.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package compress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fable = []byte(`The Frog and the Crocodile
|
||||||
|
Once, there was a frog who lived in the middle of a swamp. His entire family had lived in that swamp for generations, but this particular frog decided that he had had quite enough wetness to last him a lifetime. He decided that he was going to find a dry place to live instead.
|
||||||
|
|
||||||
|
The only thing that separated him from dry land was a swampy, muddy, swiftly flowing river. But the river was home to all sorts of slippery, slittering snakes that loved nothing better than a good, plump frog for dinner, so Frog didn't dare try to swim across.
|
||||||
|
|
||||||
|
So for many days, the frog stayed put, hopping along the bank, trying to think of a way to get across.
|
||||||
|
|
||||||
|
The snakes hissed and jeered at him, daring him to come closer, but he refused. Occasionally they would slither closer, jaws open to attack, but the frog always leaped out of the way. But no matter how far upstream he searched or how far downstream, the frog wasn't able to find a way across the water.
|
||||||
|
|
||||||
|
He had felt certain that there would be a bridge, or a place where the banks came together, yet all he found was more reeds and water. After a while, even the snakes stopped teasing him and went off in search of easier prey.
|
||||||
|
|
||||||
|
The frog sighed in frustration and sat to sulk in the rushes. Suddenly, he spotted two big eyes staring at him from the water. The giant log-shaped animal opened its mouth and asked him, "What are you doing, Frog? Surely there are enough flies right there for a meal."
|
||||||
|
|
||||||
|
The frog croaked in surprise and leaped away from the crocodile. That creature could swallow him whole in a moment without thinking about it! Once he was a satisfied that he was a safe distance away, he answered. "I'm tired of living in swampy waters, and I want to travel to the other side of the river. But if I swim across, the snakes will eat me."
|
||||||
|
|
||||||
|
The crocodile harrumphed in agreement and sat, thinking, for a while. "Well, if you're afraid of the snakes, I could give you a ride across," he suggested.
|
||||||
|
|
||||||
|
"Oh no, I don't think so," Frog answered quickly. "You'd eat me on the way over, or go underwater so the snakes could get me!"
|
||||||
|
|
||||||
|
"Now why would I let the snakes get you? I think they're a terrible nuisance with all their hissing and slithering! The river would be much better off without them altogether! Anyway, if you're so worried that I might eat you, you can ride on my tail."
|
||||||
|
|
||||||
|
The frog considered his offer. He did want to get to dry ground very badly, and there didn't seem to be any other way across the river. He looked at the crocodile from his short, squat buggy eyes and wondered about the crocodile's motives. But if he rode on the tail, the croc couldn't eat him anyway. And he was right about the snakes--no self-respecting crocodile would give a meal to the snakes.
|
||||||
|
|
||||||
|
"Okay, it sounds like a good plan to me. Turn around so I can hop on your tail."
|
||||||
|
|
||||||
|
The crocodile flopped his tail into the marshy mud and let the frog climb on, then he waddled out to the river. But he couldn't stick his tail into the water as a rudder because the frog was on it -- and if he put his tail in the water, the snakes would eat the frog. They clumsily floated downstream for a ways, until the crocodile said, "Hop onto my back so I can steer straight with my tail." The frog moved, and the journey smoothed out.
|
||||||
|
|
||||||
|
From where he was sitting, the frog couldn't see much except the back of Crocodile's head. "Why don't you hop up on my head so you can see everything around us?" Crocodile invited. `)
|
||||||
|
|
||||||
|
func BenchmarkCompressLevelMinusTwo(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CompressWithOption(fable, -2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCompressLevelNine(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CompressWithOption(fable, 9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCompressLevelMinusTwoBinary(b *testing.B) {
|
||||||
|
data := make([]byte, 1000000)
|
||||||
|
rand.Read(data)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CompressWithOption(data, -2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCompressLevelNineBinary(b *testing.B) {
|
||||||
|
data := make([]byte, 1000000)
|
||||||
|
rand.Read(data)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
CompressWithOption(data, 9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompress(t *testing.T) {
|
||||||
|
compressedB := CompressWithOption(fable, 9)
|
||||||
|
dataRateSavings := 100 * (1.0 - float64(len(compressedB))/float64(len(fable)))
|
||||||
|
fmt.Printf("Level 9: %2.0f%% percent space savings\n", dataRateSavings)
|
||||||
|
assert.True(t, len(compressedB) < len(fable))
|
||||||
|
|
||||||
|
compressedB = CompressWithOption(fable, -2)
|
||||||
|
dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(fable)))
|
||||||
|
fmt.Printf("Level -2: %2.0f%% percent space savings\n", dataRateSavings)
|
||||||
|
assert.True(t, len(compressedB) < len(fable))
|
||||||
|
}
|
187
src/croc/config.go
Normal file
187
src/croc/config.go
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
package croc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/schollz/croc/src/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
// Relay parameters
|
||||||
|
RelayWebsocketPort string
|
||||||
|
RelayTCPPorts []string
|
||||||
|
|
||||||
|
// Sender parameters
|
||||||
|
CurveType string
|
||||||
|
|
||||||
|
// Options for connecting to server
|
||||||
|
PublicServerIP string
|
||||||
|
AddressTCPPorts []string
|
||||||
|
AddressWebsocketPort string
|
||||||
|
Timeout time.Duration
|
||||||
|
LocalOnly bool
|
||||||
|
NoLocal bool
|
||||||
|
|
||||||
|
// Options for file transfering
|
||||||
|
UseEncryption bool
|
||||||
|
UseCompression bool
|
||||||
|
AllowLocalDiscovery bool
|
||||||
|
NoRecipientPrompt bool
|
||||||
|
ForceTCP bool
|
||||||
|
ForceWebsockets bool
|
||||||
|
Codephrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultConfig() Config {
|
||||||
|
c := Config{}
|
||||||
|
cr := Init(false)
|
||||||
|
c.RelayWebsocketPort = cr.RelayWebsocketPort
|
||||||
|
c.RelayTCPPorts = cr.RelayTCPPorts
|
||||||
|
c.CurveType = cr.CurveType
|
||||||
|
c.PublicServerIP = cr.Address
|
||||||
|
c.AddressTCPPorts = cr.AddressTCPPorts
|
||||||
|
c.AddressWebsocketPort = cr.AddressWebsocketPort
|
||||||
|
c.Timeout = cr.Timeout
|
||||||
|
c.LocalOnly = cr.LocalOnly
|
||||||
|
c.NoLocal = cr.NoLocal
|
||||||
|
c.UseEncryption = cr.UseEncryption
|
||||||
|
c.UseCompression = cr.UseCompression
|
||||||
|
c.AllowLocalDiscovery = cr.AllowLocalDiscovery
|
||||||
|
c.NoRecipientPrompt = cr.NoRecipientPrompt
|
||||||
|
c.ForceTCP = false
|
||||||
|
c.ForceWebsockets = false
|
||||||
|
c.Codephrase = ""
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveDefaultConfig() error {
|
||||||
|
homedir, err := homedir.Dir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
os.MkdirAll(path.Join(homedir, ".config", "croc"), 0644)
|
||||||
|
c := defaultConfig()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
toml.NewEncoder(buf).Encode(c)
|
||||||
|
confTOML := buf.String()
|
||||||
|
err = ioutil.WriteFile(path.Join(homedir, ".config", "croc", "config.toml"), []byte(confTOML), 0644)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("Default config file written at '%s'", filepath.Clean(path.Join(homedir, ".config", "croc", "config.toml")))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadConfig will override parameters
|
||||||
|
func (cr *Croc) LoadConfig() (err error) {
|
||||||
|
homedir, err := homedir.Dir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pathToConfig := path.Join(homedir, ".config", "croc", "config.toml")
|
||||||
|
if !utils.Exists(pathToConfig) {
|
||||||
|
// ignore if doesn't exist
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var c Config
|
||||||
|
_, err = toml.DecodeFile(pathToConfig, &c)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cDefault := defaultConfig()
|
||||||
|
// only load if things are different than defaults
|
||||||
|
// just in case the CLI parameters are used
|
||||||
|
if c.RelayWebsocketPort != cDefault.RelayWebsocketPort && cr.RelayWebsocketPort == cDefault.RelayWebsocketPort {
|
||||||
|
cr.RelayWebsocketPort = c.RelayWebsocketPort
|
||||||
|
fmt.Printf("loaded RelayWebsocketPort from config\n")
|
||||||
|
}
|
||||||
|
if !slicesEqual(c.RelayTCPPorts, cDefault.RelayTCPPorts) && slicesEqual(cr.RelayTCPPorts, cDefault.RelayTCPPorts) {
|
||||||
|
cr.RelayTCPPorts = c.RelayTCPPorts
|
||||||
|
fmt.Printf("loaded RelayTCPPorts from config\n")
|
||||||
|
}
|
||||||
|
if c.CurveType != cDefault.CurveType && cr.CurveType == cDefault.CurveType {
|
||||||
|
cr.CurveType = c.CurveType
|
||||||
|
fmt.Printf("loaded CurveType from config\n")
|
||||||
|
}
|
||||||
|
if c.PublicServerIP != cDefault.PublicServerIP && cr.Address == cDefault.PublicServerIP {
|
||||||
|
cr.Address = c.PublicServerIP
|
||||||
|
fmt.Printf("loaded Address from config\n")
|
||||||
|
}
|
||||||
|
if !slicesEqual(c.AddressTCPPorts, cDefault.AddressTCPPorts) {
|
||||||
|
cr.AddressTCPPorts = c.AddressTCPPorts
|
||||||
|
fmt.Printf("loaded AddressTCPPorts from config\n")
|
||||||
|
}
|
||||||
|
if c.AddressWebsocketPort != cDefault.AddressWebsocketPort && cr.AddressWebsocketPort == cDefault.AddressWebsocketPort {
|
||||||
|
cr.AddressWebsocketPort = c.AddressWebsocketPort
|
||||||
|
fmt.Printf("loaded AddressWebsocketPort from config\n")
|
||||||
|
}
|
||||||
|
if c.Timeout != cDefault.Timeout && cr.Timeout == cDefault.Timeout {
|
||||||
|
cr.Timeout = c.Timeout
|
||||||
|
fmt.Printf("loaded Timeout from config\n")
|
||||||
|
}
|
||||||
|
if c.LocalOnly != cDefault.LocalOnly && cr.LocalOnly == cDefault.LocalOnly {
|
||||||
|
cr.LocalOnly = c.LocalOnly
|
||||||
|
fmt.Printf("loaded LocalOnly from config\n")
|
||||||
|
}
|
||||||
|
if c.NoLocal != cDefault.NoLocal && cr.NoLocal == cDefault.NoLocal {
|
||||||
|
cr.NoLocal = c.NoLocal
|
||||||
|
fmt.Printf("loaded NoLocal from config\n")
|
||||||
|
}
|
||||||
|
if c.UseEncryption != cDefault.UseEncryption && cr.UseEncryption == cDefault.UseEncryption {
|
||||||
|
cr.UseEncryption = c.UseEncryption
|
||||||
|
fmt.Printf("loaded UseEncryption from config\n")
|
||||||
|
}
|
||||||
|
if c.UseCompression != cDefault.UseCompression && cr.UseCompression == cDefault.UseCompression {
|
||||||
|
cr.UseCompression = c.UseCompression
|
||||||
|
fmt.Printf("loaded UseCompression from config\n")
|
||||||
|
}
|
||||||
|
if c.AllowLocalDiscovery != cDefault.AllowLocalDiscovery && cr.AllowLocalDiscovery == cDefault.AllowLocalDiscovery {
|
||||||
|
cr.AllowLocalDiscovery = c.AllowLocalDiscovery
|
||||||
|
fmt.Printf("loaded AllowLocalDiscovery from config\n")
|
||||||
|
}
|
||||||
|
if c.NoRecipientPrompt != cDefault.NoRecipientPrompt && cr.NoRecipientPrompt == cDefault.NoRecipientPrompt {
|
||||||
|
cr.NoRecipientPrompt = c.NoRecipientPrompt
|
||||||
|
fmt.Printf("loaded NoRecipientPrompt from config\n")
|
||||||
|
}
|
||||||
|
if c.ForceWebsockets {
|
||||||
|
cr.ForceSend = 1
|
||||||
|
}
|
||||||
|
if c.ForceTCP {
|
||||||
|
cr.ForceSend = 2
|
||||||
|
}
|
||||||
|
if c.Codephrase != cDefault.Codephrase && cr.Codephrase == cDefault.Codephrase {
|
||||||
|
cr.Codephrase = c.Codephrase
|
||||||
|
fmt.Printf("loaded Codephrase from config\n")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// slicesEqual checcks if two slices are equal
|
||||||
|
// from https://stackoverflow.com/a/15312097
|
||||||
|
func slicesEqual(a, b []string) bool {
|
||||||
|
// If one is nil, the other must also be nil.
|
||||||
|
if (a == nil) != (b == nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range a {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ func init() {
|
||||||
|
|
||||||
// Croc options
|
// Croc options
|
||||||
type Croc struct {
|
type Croc struct {
|
||||||
|
// Version is the version of croc
|
||||||
|
Version string
|
||||||
// Options for all
|
// Options for all
|
||||||
Debug bool
|
Debug bool
|
||||||
// ShowText will display text on the stderr
|
// ShowText will display text on the stderr
|
||||||
|
|
7
src/croc/models.go
Normal file
7
src/croc/models.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package croc
|
||||||
|
|
||||||
|
type WebSocketMessage struct {
|
||||||
|
messageType int
|
||||||
|
message []byte
|
||||||
|
err error
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -16,6 +15,7 @@ import (
|
||||||
log "github.com/cihub/seelog"
|
log "github.com/cihub/seelog"
|
||||||
humanize "github.com/dustin/go-humanize"
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/schollz/croc/src/comm"
|
"github.com/schollz/croc/src/comm"
|
||||||
"github.com/schollz/croc/src/compress"
|
"github.com/schollz/croc/src/compress"
|
||||||
"github.com/schollz/croc/src/crypt"
|
"github.com/schollz/croc/src/crypt"
|
||||||
|
@ -26,7 +26,6 @@ import (
|
||||||
"github.com/schollz/pake"
|
"github.com/schollz/pake"
|
||||||
"github.com/schollz/progressbar/v2"
|
"github.com/schollz/progressbar/v2"
|
||||||
"github.com/schollz/spinner"
|
"github.com/schollz/spinner"
|
||||||
"github.com/tscholl2/siec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var DebugLevel string
|
var DebugLevel string
|
||||||
|
@ -38,6 +37,10 @@ func (cr *Croc) startRecipient(forceSend int, serverAddress string, tcpPorts []s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !strings.HasPrefix(err.Error(), "websocket: close 100") {
|
if !strings.HasPrefix(err.Error(), "websocket: close 100") {
|
||||||
fmt.Fprintf(os.Stderr, "\n"+err.Error())
|
fmt.Fprintf(os.Stderr, "\n"+err.Error())
|
||||||
|
cr.StateString = err.Error()
|
||||||
|
err = errors.Wrap(err, "error in recipient:")
|
||||||
|
c.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
|
@ -50,6 +53,8 @@ func (cr *Croc) receive(forceSend int, serverAddress string, tcpPorts []string,
|
||||||
var progressFile string
|
var progressFile string
|
||||||
var resumeFile bool
|
var resumeFile bool
|
||||||
var tcpConnections []comm.Comm
|
var tcpConnections []comm.Comm
|
||||||
|
var Q *pake.Pake
|
||||||
|
|
||||||
dataChan := make(chan []byte, 1024*1024)
|
dataChan := make(chan []byte, 1024*1024)
|
||||||
isConnectedIfUsingTCP := make(chan bool)
|
isConnectedIfUsingTCP := make(chan bool)
|
||||||
blocks := []string{}
|
blocks := []string{}
|
||||||
|
@ -74,20 +79,29 @@ func (cr *Croc) receive(forceSend int, serverAddress string, tcpPorts []string,
|
||||||
spin.Start()
|
spin.Start()
|
||||||
defer spin.Stop()
|
defer spin.Stop()
|
||||||
|
|
||||||
// pick an elliptic curve
|
|
||||||
curve := siec.SIEC255()
|
|
||||||
// both parties should have a weak key
|
// both parties should have a weak key
|
||||||
pw := []byte(codephrase)
|
pw := []byte(codephrase)
|
||||||
|
|
||||||
// initialize recipient Q ("1" indicates recipient)
|
// start the reader
|
||||||
Q, err := pake.Init(pw, 1, curve, 1*time.Millisecond)
|
websocketMessages := make(chan WebSocketMessage, 1024)
|
||||||
if err != nil {
|
go func() {
|
||||||
return
|
defer func() {
|
||||||
}
|
if r := recover(); r != nil {
|
||||||
|
log.Debugf("recovered from %s", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
messageType, message, err := c.ReadMessage()
|
||||||
|
websocketMessages <- WebSocketMessage{messageType, message, err}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
step := 0
|
step := 0
|
||||||
for {
|
for {
|
||||||
messageType, message, err := c.ReadMessage()
|
websocketMessage := <-websocketMessages
|
||||||
|
messageType := websocketMessage.messageType
|
||||||
|
message := websocketMessage.message
|
||||||
|
err := websocketMessage.err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -101,20 +115,44 @@ func (cr *Croc) receive(forceSend int, serverAddress string, tcpPorts []string,
|
||||||
log.Debugf("got %d: %s", messageType, message)
|
log.Debugf("got %d: %s", messageType, message)
|
||||||
switch step {
|
switch step {
|
||||||
case 0:
|
case 0:
|
||||||
// sender has initiated, sends their ip address
|
// sender has initiated, sends their initial data
|
||||||
cr.OtherIP = string(message)
|
var initialData models.Initial
|
||||||
|
err = json.Unmarshal(message, &initialData)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "incompatible versions of croc")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cr.OtherIP = initialData.IPAddress
|
||||||
log.Debugf("sender IP: %s", cr.OtherIP)
|
log.Debugf("sender IP: %s", cr.OtherIP)
|
||||||
|
|
||||||
// recipient begins by sending address
|
// check whether the version strings are compatible
|
||||||
|
versionStringsOther := strings.Split(initialData.VersionString, ".")
|
||||||
|
versionStringsSelf := strings.Split(cr.Version, ".")
|
||||||
|
if len(versionStringsOther) == 3 && len(versionStringsSelf) == 3 {
|
||||||
|
if versionStringsSelf[0] != versionStringsOther[0] || versionStringsSelf[1] != versionStringsOther[1] {
|
||||||
|
return fmt.Errorf("version sender %s is not compatible with recipient %s", cr.Version, initialData.VersionString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the PAKE with the curve sent from the sender
|
||||||
|
Q, err = pake.InitCurve(pw, 1, initialData.CurveType, 1*time.Millisecond)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "incompatible curve type")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// recipient begins by sending back initial data to sender
|
||||||
ip := ""
|
ip := ""
|
||||||
if isLocal {
|
if isLocal {
|
||||||
ip = utils.LocalIP()
|
ip = utils.LocalIP()
|
||||||
} else {
|
} else {
|
||||||
ip, _ = utils.PublicIP()
|
ip, _ = utils.PublicIP()
|
||||||
}
|
}
|
||||||
c.WriteMessage(websocket.BinaryMessage, []byte(ip))
|
initialData.VersionString = cr.Version
|
||||||
|
initialData.IPAddress = ip
|
||||||
|
bInitialData, _ := json.Marshal(initialData)
|
||||||
|
c.WriteMessage(websocket.BinaryMessage, bInitialData)
|
||||||
case 1:
|
case 1:
|
||||||
|
|
||||||
// Q receives u
|
// Q receives u
|
||||||
log.Debugf("[%d] Q computes k, sends H(k), v back to P", step)
|
log.Debugf("[%d] Q computes k, sends H(k), v back to P", step)
|
||||||
if err := Q.Update(message); err != nil {
|
if err := Q.Update(message); err != nil {
|
||||||
|
@ -130,20 +168,27 @@ func (cr *Croc) receive(forceSend int, serverAddress string, tcpPorts []string,
|
||||||
|
|
||||||
// initialize TCP connections if using (possible, but unlikely, race condition)
|
// initialize TCP connections if using (possible, but unlikely, race condition)
|
||||||
go func() {
|
go func() {
|
||||||
|
log.Debug("initializing TCP connections")
|
||||||
if !useWebsockets {
|
if !useWebsockets {
|
||||||
log.Debugf("connecting to server")
|
log.Debugf("connecting to server")
|
||||||
tcpConnections = make([]comm.Comm, len(tcpPorts))
|
tcpConnections = make([]comm.Comm, len(tcpPorts))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(tcpPorts))
|
||||||
for i, tcpPort := range tcpPorts {
|
for i, tcpPort := range tcpPorts {
|
||||||
log.Debugf("connecting to %d", i)
|
go func(i int, tcpPort string) {
|
||||||
var message string
|
defer wg.Done()
|
||||||
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
log.Debugf("connecting to %d", i)
|
||||||
if err != nil {
|
var message string
|
||||||
log.Error(err)
|
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
||||||
}
|
if err != nil {
|
||||||
if message != "recipient" {
|
log.Error(err)
|
||||||
log.Errorf("got wrong message: %s", message)
|
}
|
||||||
}
|
if message != "recipient" {
|
||||||
|
log.Errorf("got wrong message: %s", message)
|
||||||
|
}
|
||||||
|
}(i, tcpPort)
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
log.Debugf("fully connected")
|
log.Debugf("fully connected")
|
||||||
}
|
}
|
||||||
isConnectedIfUsingTCP <- true
|
isConnectedIfUsingTCP <- true
|
||||||
|
@ -385,59 +430,101 @@ func (cr *Croc) receive(forceSend int, serverAddress string, tcpPorts []string,
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
if useWebsockets {
|
if useWebsockets {
|
||||||
for {
|
for {
|
||||||
var messageType int
|
|
||||||
// read from websockets
|
// read from websockets
|
||||||
messageType, message, err = c.ReadMessage()
|
websocketMessageData := <-websocketMessages
|
||||||
if messageType != websocket.BinaryMessage {
|
if bytes.HasPrefix(websocketMessageData.message, []byte("error")) {
|
||||||
|
return fmt.Errorf("%s", websocketMessageData.message)
|
||||||
|
}
|
||||||
|
if websocketMessageData.messageType != websocket.BinaryMessage {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if bytes.Equal(message, []byte("magic")) {
|
if bytes.Equal(websocketMessageData.message, []byte("magic")) {
|
||||||
log.Debug("got magic")
|
log.Debug("got magic")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
dataChan <- message
|
dataChan <- websocketMessageData.message
|
||||||
// select {
|
|
||||||
// case dataChan <- message:
|
|
||||||
// default:
|
|
||||||
// log.Debug("blocked")
|
|
||||||
// // no message sent
|
|
||||||
// // block
|
|
||||||
// dataChan <- message
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("starting listening with tcp with %d connections", len(tcpConnections))
|
log.Debugf("starting listening with tcp with %d connections", len(tcpConnections))
|
||||||
// using TCP
|
|
||||||
var wg sync.WaitGroup
|
// check to see if any messages are sent
|
||||||
wg.Add(len(tcpConnections))
|
stopMessageSignal := make(chan bool, 1)
|
||||||
for i := range tcpConnections {
|
errorsDuringTransfer := make(chan error, 24)
|
||||||
defer func(i int) {
|
go func() {
|
||||||
log.Debugf("closing connection %d", i)
|
for {
|
||||||
tcpConnections[i].Close()
|
select {
|
||||||
}(i)
|
case sig := <-stopMessageSignal:
|
||||||
go func(wg *sync.WaitGroup, j int) {
|
errorsDuringTransfer <- nil
|
||||||
defer wg.Done()
|
log.Debugf("got message signal: %+v", sig)
|
||||||
for {
|
return
|
||||||
log.Debugf("waiting to read on %d", j)
|
case wsMessage := <-websocketMessages:
|
||||||
// read from TCP connection
|
log.Debugf("got message: %s", wsMessage.message)
|
||||||
message, _, _, err := tcpConnections[j].Read()
|
if bytes.HasPrefix(wsMessage.message, []byte("error")) {
|
||||||
// log.Debugf("message: %s", message)
|
log.Debug("stopping transfer")
|
||||||
if err != nil {
|
for i := 0; i < len(tcpConnections)+1; i++ {
|
||||||
panic(err)
|
errorsDuringTransfer <- fmt.Errorf("%s", wsMessage.message)
|
||||||
}
|
}
|
||||||
if bytes.Equal(message, []byte("magic")) {
|
|
||||||
log.Debugf("%d got magic, leaving", j)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dataChan <- message
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}(&wg, i)
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// using TCP
|
||||||
|
go func() {
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(tcpConnections))
|
||||||
|
for i := range tcpConnections {
|
||||||
|
defer func(i int) {
|
||||||
|
log.Debugf("closing connection %d", i)
|
||||||
|
tcpConnections[i].Close()
|
||||||
|
}(i)
|
||||||
|
go func(wg *sync.WaitGroup, j int) {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case _ = <-errorsDuringTransfer:
|
||||||
|
log.Debugf("%d got stop", i)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("waiting to read on %d", j)
|
||||||
|
// read from TCP connection
|
||||||
|
message, _, _, err := tcpConnections[j].Read()
|
||||||
|
// log.Debugf("message: %s", message)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if bytes.Equal(message, []byte("magic")) {
|
||||||
|
log.Debugf("%d got magic, leaving", j)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataChan <- message
|
||||||
|
}
|
||||||
|
}(&wg, i)
|
||||||
|
}
|
||||||
|
log.Debug("waiting for tcp goroutines")
|
||||||
|
wg.Wait()
|
||||||
|
errorsDuringTransfer <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
// block until this is done
|
||||||
|
|
||||||
|
log.Debug("waiting for error")
|
||||||
|
errorDuringTransfer := <-errorsDuringTransfer
|
||||||
|
log.Debug("sending stop message signal")
|
||||||
|
stopMessageSignal <- true
|
||||||
|
if errorDuringTransfer != nil {
|
||||||
|
log.Debugf("got error during transfer: %s", errorDuringTransfer.Error())
|
||||||
|
return errorDuringTransfer
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = <-finished
|
_ = <-finished
|
||||||
|
|
|
@ -26,7 +26,6 @@ import (
|
||||||
"github.com/schollz/pake"
|
"github.com/schollz/pake"
|
||||||
progressbar "github.com/schollz/progressbar/v2"
|
progressbar "github.com/schollz/progressbar/v2"
|
||||||
"github.com/schollz/spinner"
|
"github.com/schollz/spinner"
|
||||||
"github.com/tscholl2/siec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send is the async call to send data
|
// Send is the async call to send data
|
||||||
|
@ -35,10 +34,14 @@ func (cr *Croc) startSender(forceSend int, serverAddress string, tcpPorts []stri
|
||||||
log.Debugf("sending %s", fname)
|
log.Debugf("sending %s", fname)
|
||||||
err := cr.send(forceSend, serverAddress, tcpPorts, isLocal, c, fname, codephrase, useCompression, useEncryption)
|
err := cr.send(forceSend, serverAddress, tcpPorts, isLocal, c, fname, codephrase, useCompression, useEncryption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Debug(err)
|
||||||
if !strings.HasPrefix(err.Error(), "websocket: close 100") {
|
if !strings.HasPrefix(err.Error(), "websocket: close 100") {
|
||||||
fmt.Fprintf(os.Stderr, "\n"+err.Error())
|
fmt.Fprintf(os.Stderr, "\n"+err.Error())
|
||||||
|
err = errors.Wrap(err, "error in sender:")
|
||||||
|
c.WriteMessage(websocket.TextMessage, []byte(err.Error()))
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
cr.StateString = err.Error()
|
||||||
}
|
}
|
||||||
cr.StateString = err.Error()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
|
@ -91,19 +94,34 @@ func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isL
|
||||||
spin.Writer = os.Stderr
|
spin.Writer = os.Stderr
|
||||||
defer spin.Stop()
|
defer spin.Stop()
|
||||||
|
|
||||||
// pick an elliptic curve
|
|
||||||
curve := siec.SIEC255()
|
|
||||||
// both parties should have a weak key
|
// both parties should have a weak key
|
||||||
pw := []byte(codephrase)
|
pw := []byte(codephrase)
|
||||||
// initialize sender P ("0" indicates sender)
|
// initialize sender P ("0" indicates sender)
|
||||||
P, err := pake.Init(pw, 0, curve, 1*time.Millisecond)
|
P, err := pake.InitCurve(pw, 0, cr.CurveType, 1*time.Millisecond)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start the reader
|
||||||
|
websocketMessages := make(chan WebSocketMessage, 1024)
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.Debugf("recovered from %s", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
messageType, message, err := c.ReadMessage()
|
||||||
|
websocketMessages <- WebSocketMessage{messageType, message, err}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
step := 0
|
step := 0
|
||||||
for {
|
for {
|
||||||
messageType, message, errRead := c.ReadMessage()
|
websocketMessage := <-websocketMessages
|
||||||
|
messageType := websocketMessage.messageType
|
||||||
|
message := websocketMessage.message
|
||||||
|
errRead := websocketMessage.err
|
||||||
if errRead != nil {
|
if errRead != nil {
|
||||||
return errRead
|
return errRead
|
||||||
}
|
}
|
||||||
|
@ -113,6 +131,9 @@ func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isL
|
||||||
if messageType == websocket.TextMessage && bytes.Equal(message, []byte("interrupt")) {
|
if messageType == websocket.TextMessage && bytes.Equal(message, []byte("interrupt")) {
|
||||||
return errors.New("\rinterrupted by other party")
|
return errors.New("\rinterrupted by other party")
|
||||||
}
|
}
|
||||||
|
if messageType == websocket.TextMessage && bytes.HasPrefix(message, []byte("err")) {
|
||||||
|
return errors.New("\r" + string(message))
|
||||||
|
}
|
||||||
log.Debugf("got %d: %s", messageType, message)
|
log.Debugf("got %d: %s", messageType, message)
|
||||||
switch step {
|
switch step {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -123,11 +144,24 @@ func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isL
|
||||||
} else {
|
} else {
|
||||||
ip, _ = utils.PublicIP()
|
ip, _ = utils.PublicIP()
|
||||||
}
|
}
|
||||||
// send my IP address
|
|
||||||
c.WriteMessage(websocket.BinaryMessage, []byte(ip))
|
initialData := models.Initial{
|
||||||
|
CurveType: cr.CurveType,
|
||||||
|
IPAddress: ip,
|
||||||
|
VersionString: cr.Version, // version should match
|
||||||
|
}
|
||||||
|
bInitialData, _ := json.Marshal(initialData)
|
||||||
|
// send the initial data
|
||||||
|
c.WriteMessage(websocket.BinaryMessage, bInitialData)
|
||||||
case 1:
|
case 1:
|
||||||
// first receive the IP address from the sender
|
// first receive the initial data from the recipient
|
||||||
cr.OtherIP = string(message)
|
var initialData models.Initial
|
||||||
|
err = json.Unmarshal(message, &initialData)
|
||||||
|
if err != nil {
|
||||||
|
err = errors.Wrap(err, "incompatible versions of croc")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cr.OtherIP = initialData.IPAddress
|
||||||
log.Debugf("recipient IP: %s", cr.OtherIP)
|
log.Debugf("recipient IP: %s", cr.OtherIP)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -250,17 +284,23 @@ func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isL
|
||||||
go func() {
|
go func() {
|
||||||
if !useWebsockets {
|
if !useWebsockets {
|
||||||
log.Debugf("connecting to server")
|
log.Debugf("connecting to server")
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(len(tcpPorts))
|
||||||
for i, tcpPort := range tcpPorts {
|
for i, tcpPort := range tcpPorts {
|
||||||
log.Debugf("connecting to %s on connection %d", tcpPort, i)
|
go func(i int, tcpPort string) {
|
||||||
var message string
|
defer wg.Done()
|
||||||
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
log.Debugf("connecting to %s on connection %d", tcpPort, i)
|
||||||
if err != nil {
|
var message string
|
||||||
log.Error(err)
|
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
||||||
}
|
if err != nil {
|
||||||
if message != "sender" {
|
log.Error(err)
|
||||||
log.Errorf("got wrong message: %s", message)
|
}
|
||||||
}
|
if message != "sender" {
|
||||||
|
log.Errorf("got wrong message: %s", message)
|
||||||
|
}
|
||||||
|
}(i, tcpPort)
|
||||||
}
|
}
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
isConnectedIfUsingTCP <- true
|
isConnectedIfUsingTCP <- true
|
||||||
}()
|
}()
|
||||||
|
@ -395,6 +435,32 @@ func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isL
|
||||||
} else {
|
} else {
|
||||||
_ = <-isConnectedIfUsingTCP
|
_ = <-isConnectedIfUsingTCP
|
||||||
log.Debug("connected and ready to send on tcp")
|
log.Debug("connected and ready to send on tcp")
|
||||||
|
|
||||||
|
// check to see if any messages are sent
|
||||||
|
stopMessageSignal := make(chan bool, 1)
|
||||||
|
errorsDuringTransfer := make(chan error, 24)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case sig := <-stopMessageSignal:
|
||||||
|
errorsDuringTransfer <- nil
|
||||||
|
log.Debugf("got message signal: %+v", sig)
|
||||||
|
return
|
||||||
|
case wsMessage := <-websocketMessages:
|
||||||
|
log.Debugf("got message: %s", wsMessage.message)
|
||||||
|
if bytes.HasPrefix(wsMessage.message, []byte("error")) {
|
||||||
|
log.Debug("stopping transfer")
|
||||||
|
for i := 0; i < len(tcpConnections)+1; i++ {
|
||||||
|
errorsDuringTransfer <- fmt.Errorf("%s", wsMessage.message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
wg.Add(len(tcpConnections))
|
wg.Add(len(tcpConnections))
|
||||||
for i := range tcpConnections {
|
for i := range tcpConnections {
|
||||||
|
@ -405,16 +471,23 @@ func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isL
|
||||||
go func(i int, wg *sync.WaitGroup, dataChan <-chan DataChan) {
|
go func(i int, wg *sync.WaitGroup, dataChan <-chan DataChan) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
for data := range dataChan {
|
for data := range dataChan {
|
||||||
|
select {
|
||||||
|
case _ = <-errorsDuringTransfer:
|
||||||
|
log.Debugf("%d got stop", i)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
if data.err != nil {
|
if data.err != nil {
|
||||||
log.Error(data.err)
|
log.Error(data.err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cr.Bar.Add(data.bytesRead)
|
cr.Bar.Add(data.bytesRead)
|
||||||
// write data to tcp connection
|
// write data to tcp connection
|
||||||
_, err = tcpConnections[i].Write(data.b)
|
_, errTcp := tcpConnections[i].Write(data.b)
|
||||||
if err != nil {
|
if errTcp != nil {
|
||||||
err = errors.Wrap(err, "problem writing message")
|
errTcp = errors.Wrap(errTcp, "problem writing message")
|
||||||
log.Error(err)
|
log.Debug(errTcp)
|
||||||
|
errorsDuringTransfer <- errTcp
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if bytes.Equal(data.b, []byte("magic")) {
|
if bytes.Equal(data.b, []byte("magic")) {
|
||||||
|
@ -424,7 +497,18 @@ func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isL
|
||||||
}
|
}
|
||||||
}(i, &wg, dataChan)
|
}(i, &wg, dataChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// block until this is done
|
||||||
|
log.Debug("waiting for tcp goroutines")
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
log.Debug("sending stop message signal")
|
||||||
|
stopMessageSignal <- true
|
||||||
|
log.Debug("waiting for error")
|
||||||
|
errorDuringTransfer := <-errorsDuringTransfer
|
||||||
|
if errorDuringTransfer != nil {
|
||||||
|
log.Debugf("got error during transfer: %s", errorDuringTransfer.Error())
|
||||||
|
return errorDuringTransfer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cr.Bar.Finish()
|
cr.Bar.Finish()
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
log "github.com/cihub/seelog"
|
log "github.com/cihub/seelog"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"github.com/schollz/croc/src/relay"
|
"github.com/schollz/croc/src/relay"
|
||||||
|
"github.com/schollz/croc/src/utils"
|
||||||
"github.com/schollz/peerdiscovery"
|
"github.com/schollz/peerdiscovery"
|
||||||
"github.com/schollz/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send the file
|
// Send the file
|
||||||
|
@ -96,7 +96,7 @@ func (c *Croc) Receive(codephrase string) (err error) {
|
||||||
log.Debug(errDiscover)
|
log.Debug(errDiscover)
|
||||||
}
|
}
|
||||||
if len(discovered) > 0 {
|
if len(discovered) > 0 {
|
||||||
if discovered[0].Address == utils.GetLocalIP() {
|
if discovered[0].Address == utils.LocalIP() {
|
||||||
discovered[0].Address = "localhost"
|
discovered[0].Address = "localhost"
|
||||||
}
|
}
|
||||||
log.Debugf("discovered %s:%s", discovered[0].Address, discovered[0].Payload)
|
log.Debugf("discovered %s:%s", discovered[0].Address, discovered[0].Payload)
|
||||||
|
@ -153,6 +153,7 @@ func (c *Croc) sendReceive(address, websocketPort string, tcpPorts []string, fna
|
||||||
log.Debugf("connecting to %s", websocketAddress)
|
log.Debugf("connecting to %s", websocketAddress)
|
||||||
sock, _, err := websocket.DefaultDialer.Dial(websocketAddress, nil)
|
sock, _, err := websocket.DefaultDialer.Dial(websocketAddress, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer sock.Close()
|
defer sock.Close()
|
||||||
|
@ -160,6 +161,7 @@ func (c *Croc) sendReceive(address, websocketPort string, tcpPorts []string, fna
|
||||||
// tell the websockets we are connected
|
// tell the websockets we are connected
|
||||||
err = sock.WriteMessage(websocket.BinaryMessage, []byte("connected"))
|
err = sock.WriteMessage(websocket.BinaryMessage, []byte("connected"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,13 +174,14 @@ func (c *Croc) sendReceive(address, websocketPort string, tcpPorts []string, fna
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
|
log.Debug("received done signal")
|
||||||
return nil
|
return nil
|
||||||
case <-interrupt:
|
case <-interrupt:
|
||||||
if !c.Debug {
|
if !c.Debug {
|
||||||
SetDebugLevel("critical")
|
SetDebugLevel("critical")
|
||||||
}
|
}
|
||||||
log.Debug("interrupt")
|
log.Debug("interrupt")
|
||||||
err = sock.WriteMessage(websocket.TextMessage, []byte("interrupt"))
|
err = sock.WriteMessage(websocket.TextMessage, []byte("error: interrupted by other party"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
7
src/models/initial.go
Normal file
7
src/models/initial.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Initial struct {
|
||||||
|
CurveType string
|
||||||
|
IPAddress string
|
||||||
|
VersionString string
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue