Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
166b1cf6cc | ||
|
|
0f72e35268 | ||
|
|
0b302b3f7c | ||
|
|
0753246a4b | ||
|
|
59bd382bf5 | ||
|
|
1ae7a2ff97 | ||
|
|
2cf9480302 | ||
|
|
7b68bcaea2 | ||
|
|
382ef3157a | ||
|
|
0f3c772ca8 | ||
|
|
efd8c36d2e | ||
|
|
05a4e90208 |
|
|
@ -12,9 +12,6 @@ Status"></a>
|
|||
<a href="https://saythanks.io/to/schollz"><img src="https://img.shields.io/badge/Say%20Thanks-!-brightgreen.svg?style=flat-square" alt="Say thanks"></a>
|
||||
</p>
|
||||
|
||||
|
||||
<p align="center"><code>curl https://getcroc.schollz.com | bash</code></p>
|
||||
|
||||
`croc` is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, *croc* is the only CLI file-transfer tool does **all** of the following:
|
||||
|
||||
- allows **any two computers** to transfer data (using a relay)
|
||||
|
|
|
|||
7
go.mod
7
go.mod
|
|
@ -8,6 +8,7 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/kalafut/imohash v1.0.0
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
|
|
@ -15,17 +16,17 @@ require (
|
|||
github.com/pkg/errors v0.8.1
|
||||
github.com/schollz/logger v1.0.1
|
||||
github.com/schollz/mnemonicode v1.0.1
|
||||
github.com/schollz/pake v1.1.1
|
||||
github.com/schollz/pake/v2 v2.0.2
|
||||
github.com/schollz/peerdiscovery v1.4.1
|
||||
github.com/schollz/progressbar/v2 v2.14.2
|
||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/tscholl2/siec v0.0.0-20191103131401-2e0c53a9e212 // indirect
|
||||
github.com/tscholl2/siec v0.0.0-20191103131401-2e0c53a9e212
|
||||
github.com/urfave/cli v1.22.1
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f
|
||||
golang.org/x/net v0.0.0-20191116160921-f9c825593386 // indirect
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777 // indirect
|
||||
golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
||||
|
|
|
|||
11
go.sum
11
go.sum
|
|
@ -16,6 +16,8 @@ github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMS
|
|||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/kalafut/imohash v1.0.0 h1:LgCJ+p/BwM2HKpOxFopkeddpzVCfm15EtXMroXD1SYE=
|
||||
github.com/kalafut/imohash v1.0.0/go.mod h1:c3RHT80ZAp5C/aYgQI92ZlrOymqkZnRDprU87kg75HI=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
|
|
@ -41,8 +43,8 @@ github.com/schollz/logger v1.0.1 h1:BuBAU+euqphM0Ny9qFVScl4RSxatis4nCHIkOxO2cUU=
|
|||
github.com/schollz/logger v1.0.1/go.mod h1:P6F4/dGMGcx8wh+kG1zrNEd4vnNpEBY/mwEMd/vn6AM=
|
||||
github.com/schollz/mnemonicode v1.0.1 h1:LiH5hwADZwjwnfXsaD4xgnMyTAtaKHN+e5AyjRU6WSU=
|
||||
github.com/schollz/mnemonicode v1.0.1/go.mod h1:cl4UAOhUV0mkdjMj/QYaUZbZZdF8BnOqoz8rHMzwboY=
|
||||
github.com/schollz/pake v1.1.1 h1:QKeojDWzdAdtRC4m89b6HAxw/8gjqrVu7r4SAOxOFg8=
|
||||
github.com/schollz/pake v1.1.1/go.mod h1:aWMxQ1jwqZRwk3StflHcdyzPR+CyW5W7+WIZD6Y3dEY=
|
||||
github.com/schollz/pake/v2 v2.0.2 h1:p9y4Gocc5PWueyhhR7OH+Gwpu2xkP5BM9Pepl9krVfo=
|
||||
github.com/schollz/pake/v2 v2.0.2/go.mod h1:3uXB571UYJ8Eqh2EEohXe/aO32QID+Varb4GeYA//yw=
|
||||
github.com/schollz/peerdiscovery v1.4.1 h1:xtZ/D8/4eq9O6UEhRupZZiJm4BA8+u1IVUgeHo5VPm4=
|
||||
github.com/schollz/peerdiscovery v1.4.1/go.mod h1:WDdk0/JVyVHVIA/bmhzTkUg32dhJ20O4tExNqV1u6sk=
|
||||
github.com/schollz/progressbar/v2 v2.13.2 h1:3L9bP5KQOGEnFP8P5V8dz+U0yo5I29iY5Oa9s9EAwn0=
|
||||
|
|
@ -69,8 +71,7 @@ github.com/tscholl2/siec v0.0.0-20191103131401-2e0c53a9e212/go.mod h1:KL9+ubr1JZ
|
|||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190907121410-71b5226ff739/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4=
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
|
|
@ -86,6 +87,8 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777 h1:wejkGHRTr38uaKRqECZlsCsJ1/TGxIyFbH32x5zUdu4=
|
||||
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169 h1:LPLFLulk2vyM7yI3CwNW64O6e8AxBmr9opfv14yI7HI=
|
||||
golang.org/x/sys v0.0.0-20191118133127-cf1e2d577169/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
$env:GOOS="js"; $env:GOARCH="wasm"; go build -v -o main.wasm
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
</html>
|
||||
|
||||
<body>
|
||||
<script src="wasm_exec.js"></script>
|
||||
<script>
|
||||
const go = new Go();
|
||||
WebAssembly.instantiateStreaming(fetch("main.wasm"), go.importObject).then((result) => {
|
||||
go.run(result.instance);
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
let log = msg => {
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
// websockets
|
||||
var socket;
|
||||
var checkErr = e => {
|
||||
if (typeof e === 'error') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
var croc = {
|
||||
"key": "",
|
||||
"pake": "",
|
||||
};
|
||||
var sharedPassphrase = "pass123"
|
||||
console.log(sharedPassphrase)
|
||||
|
||||
const socketMessageListener = (event) => {
|
||||
console.log(event);
|
||||
if (event.data == "initiated") {
|
||||
socket.send(JSON.stringify({ "m": "room", "ps": sharedPassphrase }))
|
||||
} else if (event.data == "initpake") {
|
||||
croc.pake = pakeInit(sharedPassphrase, "0");
|
||||
socket.send(
|
||||
messageEncode(croc.key, JSON.stringify({ "t": "pake", "b": croc.pake }))
|
||||
)
|
||||
} else {
|
||||
msgJSON = messageDecode(croc.key, event.data);
|
||||
console.log(msgJSON);
|
||||
}
|
||||
};
|
||||
const socketOpenListener = (event) => {
|
||||
log('connected to websockets');
|
||||
socket.send("receive");
|
||||
}
|
||||
const socketCloseListener = (event) => {
|
||||
if (socket) {
|
||||
log('Disconnected.');
|
||||
}
|
||||
var ws_url = window.origin.replace("http", "ws") + '/ws';
|
||||
log(`connecting to '${ws_url}'`)
|
||||
socket = new WebSocket(ws_url);
|
||||
socket.addEventListener('open', socketOpenListener);
|
||||
socket.addEventListener('message', socketMessageListener);
|
||||
socket.addEventListener('close', socketCloseListener);
|
||||
};
|
||||
socketCloseListener();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package main
|
||||
|
||||
//go:generate cp /usr/local/go/misc/wasm/wasm_exec.js .
|
||||
|
||||
// compile with
|
||||
// GOOS=js GOARCH=wasm go build -o main.wasm
|
||||
|
||||
// to run
|
||||
//
|
||||
// bob = pakeInit("pass1","0");
|
||||
// jane = pakeInit("pass1","1");
|
||||
// jane = pakeUpdate(jane,pakePublic(bob));
|
||||
// bob = pakeUpdate(bob,pakePublic(jane));
|
||||
// jane = pakeUpdate(jane,pakePublic(bob));
|
||||
// keyAndSalt = JSON.parse(pakeSessionKey(bob,""))
|
||||
// console.log(pakeSessionKey(jane,keyAndSalt.Salt))
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/v6/src/compress"
|
||||
"github.com/schollz/croc/v6/src/croc"
|
||||
"github.com/schollz/croc/v6/src/crypt"
|
||||
"github.com/schollz/croc/v6/src/message"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/schollz/pake/v2"
|
||||
)
|
||||
|
||||
func marshal(a interface{}) string {
|
||||
b, err := json.Marshal(a)
|
||||
if err != nil {
|
||||
log.Errorf("could not marshal: %+v, because '%s'", a, err.Error())
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
// crocInit(weakPassphrase,issender)
|
||||
func crocInit(this js.Value, inputs []js.Value) interface{} {
|
||||
if len(inputs) != 2 {
|
||||
return js.Global().Get("Error").New("need weakPassphrase,issender")
|
||||
}
|
||||
c := new(croc.Client)
|
||||
c.FilesHasFinished = make(map[int]struct{})
|
||||
c.Options = croc.Options{
|
||||
IsSender: inputs[1].String() == "1",
|
||||
SharedSecret: inputs[0].String(),
|
||||
RelayAddress: "localhost:9009",
|
||||
Debug: true,
|
||||
}
|
||||
return marshal(c)
|
||||
}
|
||||
|
||||
// messageEncode(key, Message)
|
||||
// returns base64 encoded, encrypts if key != nil
|
||||
func messageEncode(this js.Value, inputs []js.Value) interface{} {
|
||||
// initialize sender P ("1" indicates sender)
|
||||
if len(inputs) != 2 {
|
||||
return js.Global().Get("Error").New("need weakPassphrase, role")
|
||||
}
|
||||
var m message.Message
|
||||
err := json.Unmarshal([]byte(inputs[1].String()), &m)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
b = compress.Compress(b)
|
||||
if inputs[0].String() != "" {
|
||||
var key []byte
|
||||
key, err = base64.StdEncoding.DecodeString(inputs[0].String())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
|
||||
b, err = crypt.Encrypt(b, key)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
// messageDecode(key, encodedMessage)
|
||||
// returns base64 encoded, encrypts if key != nil
|
||||
func messageDecode(this js.Value, inputs []js.Value) interface{} {
|
||||
// initialize sender P ("1" indicates sender)
|
||||
if len(inputs) != 2 {
|
||||
return js.Global().Get("Error").New("need key, encodedmessage")
|
||||
}
|
||||
var key []byte
|
||||
key = nil
|
||||
var err error
|
||||
if inputs[0].String() != "" {
|
||||
key, err = base64.StdEncoding.DecodeString(inputs[0].String())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(inputs[1].String())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
m, err := message.Decode(key, b)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
|
||||
bJSON, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
return string(bJSON)
|
||||
}
|
||||
|
||||
// pakeInit(weakPassphrase, role)
|
||||
// returns: pakeBytes
|
||||
func pakeInit(this js.Value, inputs []js.Value) interface{} {
|
||||
// initialize sender P ("0" indicates sender)
|
||||
if len(inputs) != 2 {
|
||||
return js.Global().Get("Error").New("need weakPassphrase, role")
|
||||
}
|
||||
role := 0
|
||||
if inputs[1].String() == "1" {
|
||||
role = 1
|
||||
}
|
||||
P, err := pake.Init([]byte(inputs[0].String()), role, elliptic.P521(), 1*time.Microsecond)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
bJSON, err := json.Marshal(P)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(bJSON)
|
||||
}
|
||||
|
||||
// pakeUpdate(pakeBytes,otherPublicPakeBytes)
|
||||
func pakeUpdate(this js.Value, inputs []js.Value) interface{} {
|
||||
if len(inputs) != 2 {
|
||||
return js.Global().Get("Error").New("need two input")
|
||||
}
|
||||
var P *pake.Pake
|
||||
|
||||
b, err := base64.StdEncoding.DecodeString(inputs[0].String())
|
||||
if err != nil {
|
||||
log.Errorf("problem with %s: %s", inputs[0].String(), err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
err = json.Unmarshal(b, &P)
|
||||
P.SetCurve(elliptic.P521())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
|
||||
qbytes, err := base64.StdEncoding.DecodeString(inputs[1].String())
|
||||
if err != nil {
|
||||
log.Errorf("problem with %s: %s", inputs[1].String(), err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
err = P.Update(qbytes)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
bJSON, err := json.Marshal(P)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(bJSON)
|
||||
}
|
||||
|
||||
// pakePublic(pakeBytes)
|
||||
func pakePublic(this js.Value, inputs []js.Value) interface{} {
|
||||
var P *pake.Pake
|
||||
b, err := base64.StdEncoding.DecodeString(inputs[0].String())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
err = json.Unmarshal(b, &P)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
P.SetCurve(elliptic.P521())
|
||||
return base64.StdEncoding.EncodeToString(P.Bytes())
|
||||
}
|
||||
|
||||
// pakeSessionKey(pakeBytes,salt)
|
||||
func pakeSessionKey(this js.Value, inputs []js.Value) interface{} {
|
||||
if len(inputs) != 2 {
|
||||
return js.Global().Get("Error").New("need two input")
|
||||
}
|
||||
var P *pake.Pake
|
||||
b, err := base64.StdEncoding.DecodeString(inputs[0].String())
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not decode pakeBytes")
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
err = json.Unmarshal(b, &P)
|
||||
P.SetCurve(elliptic.P521())
|
||||
if err != nil {
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
key, err := P.SessionKey()
|
||||
if err != nil {
|
||||
return js.Global().Get("Error").New(err.Error())
|
||||
}
|
||||
|
||||
type KeyAndSalt struct {
|
||||
Key string
|
||||
Salt string
|
||||
}
|
||||
|
||||
var kas KeyAndSalt
|
||||
var salt []byte
|
||||
salt = nil
|
||||
if len(inputs[1].String()) > 0 {
|
||||
b, errb := base64.StdEncoding.DecodeString(inputs[1].String())
|
||||
if errb != nil {
|
||||
return js.Global().Get("Error").New(errb.Error())
|
||||
}
|
||||
salt = b
|
||||
}
|
||||
|
||||
cryptKey, cryptSalt, err := crypt.New(key, salt)
|
||||
|
||||
kas.Key = base64.StdEncoding.EncodeToString(cryptKey)
|
||||
kas.Salt = base64.StdEncoding.EncodeToString(cryptSalt)
|
||||
b, _ = json.Marshal(kas)
|
||||
|
||||
log.Debugf("key: %x", cryptKey)
|
||||
log.Debugf("salt: %x", cryptSalt)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan bool)
|
||||
// fmt.Println("starting")
|
||||
js.Global().Set("crocInit", js.FuncOf(crocInit))
|
||||
// js.Global().Set("pakeInit", js.FuncOf(pakeInit))
|
||||
// js.Global().Set("pakePublic", js.FuncOf(pakePublic))
|
||||
// js.Global().Set("pakeUpdate", js.FuncOf(pakeUpdate))
|
||||
// js.Global().Set("pakeSessionKey", js.FuncOf(pakeSessionKey))
|
||||
// js.Global().Set("messageEncode", js.FuncOf(messageEncode))
|
||||
// js.Global().Set("messageDecode", js.FuncOf(messageDecode))
|
||||
fmt.Println("Initiated")
|
||||
<-c
|
||||
}
|
||||
|
|
@ -0,0 +1,533 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
(() => {
|
||||
// Map multiple JavaScript environments to a single common API,
|
||||
// preferring web standards over Node.js API.
|
||||
//
|
||||
// Environments considered:
|
||||
// - Browsers
|
||||
// - Node.js
|
||||
// - Electron
|
||||
// - Parcel
|
||||
|
||||
if (typeof global !== "undefined") {
|
||||
// global already exists
|
||||
} else if (typeof window !== "undefined") {
|
||||
window.global = window;
|
||||
} else if (typeof self !== "undefined") {
|
||||
self.global = self;
|
||||
} else {
|
||||
throw new Error("cannot export Go (neither global, window nor self is defined)");
|
||||
}
|
||||
|
||||
if (!global.require && typeof require !== "undefined") {
|
||||
global.require = require;
|
||||
}
|
||||
|
||||
if (!global.fs && global.require) {
|
||||
global.fs = require("fs");
|
||||
}
|
||||
|
||||
if (!global.fs) {
|
||||
let outputBuf = "";
|
||||
global.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substr(0, nl));
|
||||
outputBuf = outputBuf.substr(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
throw new Error("not implemented");
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
open(path, flags, mode, callback) {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
callback(err);
|
||||
},
|
||||
read(fd, buffer, offset, length, position, callback) {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
callback(err);
|
||||
},
|
||||
fsync(fd, callback) {
|
||||
callback(null);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.crypto) {
|
||||
const nodeCrypto = require("crypto");
|
||||
global.crypto = {
|
||||
getRandomValues(b) {
|
||||
nodeCrypto.randomFillSync(b);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.performance) {
|
||||
global.performance = {
|
||||
now() {
|
||||
const [sec, nsec] = process.hrtime();
|
||||
return sec * 1000 + nsec / 1000000;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.TextEncoder) {
|
||||
global.TextEncoder = require("util").TextEncoder;
|
||||
}
|
||||
|
||||
if (!global.TextDecoder) {
|
||||
global.TextDecoder = require("util").TextDecoder;
|
||||
}
|
||||
|
||||
// End of polyfills for common API.
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
global.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this.exit = (code) => {
|
||||
if (code !== 0) {
|
||||
console.warn("exit code:", code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise((resolve) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const mem = () => {
|
||||
// The buffer may change when requesting more memory.
|
||||
return new DataView(this._inst.exports.mem.buffer);
|
||||
}
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
mem().setUint32(addr + 0, v, true);
|
||||
mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = mem().getUint32(addr + 0, true);
|
||||
const high = mem().getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = mem().getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = mem().getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number") {
|
||||
if (isNaN(v)) {
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
if (v === 0) {
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 1, true);
|
||||
return;
|
||||
}
|
||||
mem().setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (v) {
|
||||
case undefined:
|
||||
mem().setFloat64(addr, 0, true);
|
||||
return;
|
||||
case null:
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 2, true);
|
||||
return;
|
||||
case true:
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 3, true);
|
||||
return;
|
||||
case false:
|
||||
mem().setUint32(addr + 4, nanHead, true);
|
||||
mem().setUint32(addr, 4, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let ref = this._refs.get(v);
|
||||
if (ref === undefined) {
|
||||
ref = this._values.length;
|
||||
this._values.push(v);
|
||||
this._refs.set(v, ref);
|
||||
}
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "string":
|
||||
typeFlag = 1;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
}
|
||||
mem().setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
mem().setUint32(addr, ref, true);
|
||||
}
|
||||
|
||||
const loadSlice = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (addr) => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
go: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
"runtime.wasmExit": (sp) => {
|
||||
const code = mem().getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._refs;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
"runtime.wasmWrite": (sp) => {
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = mem().getInt32(sp + 24, true);
|
||||
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||
},
|
||||
|
||||
// func nanotime() int64
|
||||
"runtime.nanotime": (sp) => {
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
"runtime.walltime": (sp) => {
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(id, setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
|
||||
));
|
||||
mem().setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
"runtime.clearTimeoutEvent": (sp) => {
|
||||
const id = mem().getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
"runtime.getRandomData": (sp) => {
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (sp) => {
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (sp) => {
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp(); // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (sp) => {
|
||||
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (sp) => {
|
||||
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (sp) => {
|
||||
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (sp) => {
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp(); // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
mem().setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
storeValue(sp + 56, err);
|
||||
mem().setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (sp) => {
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp(); // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
mem().setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
storeValue(sp + 40, err);
|
||||
mem().setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (sp) => {
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp(); // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
mem().setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
storeValue(sp + 40, err);
|
||||
mem().setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (sp) => {
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (sp) => {
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (sp) => {
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (sp) => {
|
||||
mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (sp) => {
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (!(src instanceof Uint8Array)) {
|
||||
mem().setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
mem().setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
"syscall/js.copyBytesToJS": (sp) => {
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (!(dst instanceof Uint8Array)) {
|
||||
mem().setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
mem().setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
"debug": (value) => {
|
||||
console.log(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
this._inst = instance;
|
||||
this._values = [ // TODO: garbage collection
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
this,
|
||||
];
|
||||
this._refs = new Map();
|
||||
this.exited = false;
|
||||
|
||||
const mem = new DataView(this._inst.exports.mem.buffer)
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = (str) => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + "\0");
|
||||
new Uint8Array(mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach((arg) => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
argvPtrs.push(keys.length);
|
||||
keys.forEach((key) => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach((ptr) => {
|
||||
mem.setUint32(offset, ptr, true);
|
||||
mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
global.require &&
|
||||
global.require.main === module &&
|
||||
global.process &&
|
||||
global.process.versions &&
|
||||
!global.process.versions.electron
|
||||
) {
|
||||
if (process.argv.length < 3) {
|
||||
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
go.argv = process.argv.slice(2);
|
||||
go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env);
|
||||
go.exit = process.exit;
|
||||
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
|
||||
process.on("exit", (code) => { // Node.js exits if no event handler is pending
|
||||
if (code === 0 && !go.exited) {
|
||||
// deadlock, make Go print error and stack traces
|
||||
go._pendingEvent = { id: 0 };
|
||||
go._resume();
|
||||
}
|
||||
});
|
||||
return go.run(result.instance);
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/schollz/croc/v6/src/models"
|
||||
"github.com/schollz/croc/v6/src/tcp"
|
||||
"github.com/schollz/croc/v6/src/utils"
|
||||
"github.com/schollz/croc/v6/src/webrelay"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
|
@ -51,6 +52,7 @@ func Run() (err error) {
|
|||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "code, c", Usage: "codephrase used to connect to relay"},
|
||||
cli.BoolFlag{Name: "no-local", Usage: "disable local relay when sending"},
|
||||
cli.BoolFlag{Name: "no-multi", Usage: "disable multiplexing"},
|
||||
cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the local relay (optional)"},
|
||||
},
|
||||
HelpName: "croc send",
|
||||
|
|
@ -70,6 +72,18 @@ func Run() (err error) {
|
|||
cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "web",
|
||||
Usage: "start your own web relay (optional)",
|
||||
Description: "start web relay",
|
||||
HelpName: "croc web",
|
||||
Action: func(c *cli.Context) error {
|
||||
return startWebRelay(c)
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "port", Value: "9014", Usage: "port of the web relay"},
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
cli.BoolFlag{Name: "remember", Usage: "save these settings to reuse next time"},
|
||||
|
|
@ -136,15 +150,16 @@ func getConfigFile() string {
|
|||
func send(c *cli.Context) (err error) {
|
||||
setDebugLevel(c)
|
||||
crocOptions := croc.Options{
|
||||
SharedSecret: c.String("code"),
|
||||
IsSender: true,
|
||||
Debug: c.GlobalBool("debug"),
|
||||
NoPrompt: c.GlobalBool("yes"),
|
||||
RelayAddress: c.GlobalString("relay"),
|
||||
Stdout: c.GlobalBool("stdout"),
|
||||
DisableLocal: c.Bool("no-local"),
|
||||
RelayPorts: strings.Split(c.String("ports"), ","),
|
||||
Ask: c.GlobalBool("ask"),
|
||||
SharedSecret: c.String("code"),
|
||||
IsSender: true,
|
||||
Debug: c.GlobalBool("debug"),
|
||||
NoPrompt: c.GlobalBool("yes"),
|
||||
RelayAddress: c.GlobalString("relay"),
|
||||
Stdout: c.GlobalBool("stdout"),
|
||||
DisableLocal: c.Bool("no-local"),
|
||||
RelayPorts: strings.Split(c.String("ports"), ","),
|
||||
Ask: c.GlobalBool("ask"),
|
||||
NoMultiplexing: c.Bool("no-multi"),
|
||||
}
|
||||
b, errOpen := ioutil.ReadFile(getConfigFile())
|
||||
if errOpen == nil && !c.GlobalBool("remember") {
|
||||
|
|
@ -381,3 +396,12 @@ func relay(c *cli.Context) (err error) {
|
|||
}
|
||||
return tcp.Run(debugString, ports[0], tcpPorts)
|
||||
}
|
||||
|
||||
func startWebRelay(c *cli.Context) (err error) {
|
||||
debugString := "info"
|
||||
if c.GlobalBool("debug") {
|
||||
debugString = "debug"
|
||||
}
|
||||
|
||||
return webrelay.Run(debugString, c.String("port"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package croc
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
|
|
@ -17,7 +16,6 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/compress"
|
||||
|
|
@ -27,10 +25,11 @@ import (
|
|||
"github.com/schollz/croc/v6/src/tcp"
|
||||
"github.com/schollz/croc/v6/src/utils"
|
||||
log "github.com/schollz/logger"
|
||||
"github.com/schollz/pake"
|
||||
"github.com/schollz/pake/v2"
|
||||
"github.com/schollz/peerdiscovery"
|
||||
"github.com/schollz/progressbar/v2"
|
||||
"github.com/schollz/spinner"
|
||||
"github.com/tscholl2/siec"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -48,22 +47,23 @@ func Debug(debug bool) {
|
|||
|
||||
// Options specifies user specific options
|
||||
type Options struct {
|
||||
IsSender bool
|
||||
SharedSecret string
|
||||
Debug bool
|
||||
RelayAddress string
|
||||
RelayPorts []string
|
||||
Stdout bool
|
||||
NoPrompt bool
|
||||
DisableLocal bool
|
||||
Ask bool
|
||||
IsSender bool
|
||||
SharedSecret string
|
||||
Debug bool
|
||||
RelayAddress string
|
||||
RelayPorts []string
|
||||
Stdout bool
|
||||
NoPrompt bool
|
||||
NoMultiplexing bool
|
||||
DisableLocal bool
|
||||
Ask bool
|
||||
}
|
||||
|
||||
// Client holds the state of the croc transfer
|
||||
type Client struct {
|
||||
Options Options
|
||||
Pake *pake.Pake
|
||||
Key crypt.Encryption
|
||||
Key []byte
|
||||
ExternalIP, ExternalIPConnected string
|
||||
|
||||
// steps involved in forming relationship
|
||||
|
|
@ -147,17 +147,11 @@ func New(ops Options) (c *Client, err error) {
|
|||
|
||||
c.conn = make([]*comm.Comm, 16)
|
||||
|
||||
// use default key (no encryption, until PAKE succeeds)
|
||||
c.Key, err = crypt.New(nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// initialize pake
|
||||
if c.Options.IsSender {
|
||||
c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 1, elliptic.P521(), 1*time.Microsecond)
|
||||
c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 1, siec.SIEC255(), 1*time.Microsecond)
|
||||
} else {
|
||||
c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 0, elliptic.P521(), 1*time.Microsecond)
|
||||
c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 0, siec.SIEC255(), 1*time.Microsecond)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -292,6 +286,10 @@ func (c *Client) transferOverLocalRelay(options TransferOptions, errchan chan<-
|
|||
log.Debug("exchanged header message")
|
||||
c.Options.RelayAddress = "localhost"
|
||||
c.Options.RelayPorts = strings.Split(banner, ",")
|
||||
if c.Options.NoMultiplexing {
|
||||
log.Debug("no multiplexing")
|
||||
c.Options.RelayPorts = []string{c.Options.RelayPorts[0]}
|
||||
}
|
||||
c.ExternalIP = ipaddr
|
||||
errchan <- c.transfer(options)
|
||||
}
|
||||
|
|
@ -309,7 +307,7 @@ func (c *Client) Send(options TransferOptions) (err error) {
|
|||
}
|
||||
fmt.Fprintf(os.Stderr, "Code is: %s\nOn the other computer run\n\ncroc %s%s\n", c.Options.SharedSecret, otherRelay, c.Options.SharedSecret)
|
||||
if c.Options.Ask {
|
||||
machid, _ := machineid.ID()
|
||||
machid := "machineid.ID()"
|
||||
fmt.Fprintf(os.Stderr, "\rYour machine ID is '%s'\n", machid)
|
||||
}
|
||||
// // c.spinner.Suffix = " waiting for recipient..."
|
||||
|
|
@ -369,6 +367,10 @@ func (c *Client) Send(options TransferOptions) (err error) {
|
|||
|
||||
c.conn[0] = conn
|
||||
c.Options.RelayPorts = strings.Split(banner, ",")
|
||||
if c.Options.NoMultiplexing {
|
||||
log.Debug("no multiplexing")
|
||||
c.Options.RelayPorts = []string{c.Options.RelayPorts[0]}
|
||||
}
|
||||
c.ExternalIP = ipaddr
|
||||
log.Debug("exchanged header message")
|
||||
errchan <- c.transfer(options)
|
||||
|
|
@ -470,6 +472,10 @@ func (c *Client) Receive() (err error) {
|
|||
|
||||
c.conn[0].Send([]byte("handshake"))
|
||||
c.Options.RelayPorts = strings.Split(banner, ",")
|
||||
if c.Options.NoMultiplexing {
|
||||
log.Debug("no multiplexing")
|
||||
c.Options.RelayPorts = []string{c.Options.RelayPorts[0]}
|
||||
}
|
||||
log.Debug("exchanged header message")
|
||||
fmt.Fprintf(os.Stderr, "\rsecuring channel...")
|
||||
return c.transfer(TransferOptions{})
|
||||
|
|
@ -551,7 +557,7 @@ func (c *Client) processMessageFileInfo(m message.Message) (done bool, err error
|
|||
// c.spinner.Stop()
|
||||
if !c.Options.NoPrompt || c.Options.Ask || senderInfo.Ask {
|
||||
if c.Options.Ask || senderInfo.Ask {
|
||||
machID, _ := machineid.ID()
|
||||
machID := "machineid.ID()"
|
||||
fmt.Fprintf(os.Stderr, "\rYour machine id is '%s'.\nAccept %s (%s) from '%s'? (y/n) ", machID, fname, utils.ByteCountDecimal(totalSize), senderInfo.MachineID)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "\rAccept %s (%s)? (y/n) ", fname, utils.ByteCountDecimal(totalSize))
|
||||
|
|
@ -648,10 +654,11 @@ func (c *Client) processMessageSalt(m message.Message) (done bool, err error) {
|
|||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
c.Key, err = crypt.New(key, m.Bytes)
|
||||
c.Key, _, err = crypt.New(key, m.Bytes)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
log.Debugf("key = %+x", c.Key)
|
||||
if c.ExternalIPConnected == "" {
|
||||
// it can be preset by the local relay
|
||||
c.ExternalIPConnected = m.Message
|
||||
|
|
@ -742,7 +749,7 @@ func (c *Client) processMessage(payload []byte) (done bool, err error) {
|
|||
func (c *Client) updateIfSenderChannelSecured() (err error) {
|
||||
if c.Options.IsSender && c.Step1ChannelSecured && !c.Step2FileInfoTransfered {
|
||||
var b []byte
|
||||
machID, _ := machineid.ID()
|
||||
machID := "machineid.ID()"
|
||||
b, err = json.Marshal(SenderInfo{
|
||||
FilesToTransfer: c.FilesToTransfer,
|
||||
MachineID: machID,
|
||||
|
|
@ -835,7 +842,7 @@ func (c *Client) recipientGetFileReady(finished bool) (err error) {
|
|||
}
|
||||
|
||||
c.TotalSent = 0
|
||||
machID, _ := machineid.ID()
|
||||
machID := "machineid.ID()"
|
||||
bRequest, _ := json.Marshal(RemoteFileRequest{
|
||||
CurrentFileChunkRanges: c.CurrentFileChunkRanges,
|
||||
FilesToTransferCurrentNum: c.FilesToTransferCurrentNum,
|
||||
|
|
@ -1040,7 +1047,7 @@ func (c *Client) receiveData(i int) {
|
|||
break
|
||||
}
|
||||
|
||||
data, err = c.Key.Decrypt(data)
|
||||
data, err = crypt.Decrypt(data, c.Key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -1126,10 +1133,11 @@ func (c *Client) sendData(i int) {
|
|||
posByte := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(posByte, pos)
|
||||
|
||||
dataToSend, err := c.Key.Encrypt(
|
||||
dataToSend, err := crypt.Encrypt(
|
||||
compress.Compress(
|
||||
append(posByte, data[:n]...),
|
||||
),
|
||||
c.Key,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func TestCroc(t *testing.T) {
|
|||
go tcp.Run("debug", "8083")
|
||||
go tcp.Run("debug", "8084")
|
||||
go tcp.Run("debug", "8085")
|
||||
time.Sleep(1 * time.Second)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
log.Debug("setting up sender")
|
||||
sender, err := New(Options{
|
||||
|
|
@ -36,6 +36,7 @@ func TestCroc(t *testing.T) {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
log.Debug("setting up receiver")
|
||||
receiver, err := New(Options{
|
||||
|
|
|
|||
|
|
@ -5,56 +5,37 @@ import (
|
|||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// Encryption is the basic type for storing
|
||||
// the key, passphrase and salt
|
||||
type Encryption struct {
|
||||
key []byte
|
||||
passphrase []byte
|
||||
salt []byte
|
||||
}
|
||||
|
||||
// New generates a new Encryption, using the supplied passphrase and
|
||||
// an optional supplied salt.
|
||||
// Passing nil passphrase will not use decryption.
|
||||
func New(passphrase []byte, salt []byte) (e Encryption, err error) {
|
||||
if passphrase == nil {
|
||||
e = Encryption{nil, nil, nil}
|
||||
// New generates a new key based on a passphrase and salt
|
||||
func New(passphrase []byte, usersalt []byte) (key []byte, salt []byte, err error) {
|
||||
if len(passphrase) < 1 {
|
||||
err = fmt.Errorf("need more than that for passphrase")
|
||||
return
|
||||
}
|
||||
e.passphrase = passphrase
|
||||
if salt == nil {
|
||||
e.salt = make([]byte, 8)
|
||||
if usersalt == nil {
|
||||
salt = make([]byte, 8)
|
||||
// http://www.ietf.org/rfc/rfc2898.txt
|
||||
// Salt.
|
||||
rand.Read(e.salt)
|
||||
rand.Read(salt)
|
||||
} else {
|
||||
e.salt = salt
|
||||
salt = usersalt
|
||||
}
|
||||
e.key = pbkdf2.Key([]byte(passphrase), e.salt, 100, 32, sha256.New)
|
||||
key = pbkdf2.Key([]byte(passphrase), salt, 100, 32, sha256.New)
|
||||
return
|
||||
}
|
||||
|
||||
// Salt returns the salt bytes
|
||||
func (e Encryption) Salt() []byte {
|
||||
return e.salt
|
||||
}
|
||||
|
||||
// Encrypt will generate an Encryption, prefixed with the IV
|
||||
func (e Encryption) Encrypt(plaintext []byte) (encrypted []byte, err error) {
|
||||
if e.passphrase == nil {
|
||||
encrypted = plaintext
|
||||
return
|
||||
}
|
||||
// Encrypt will encrypt using the pre-generated key
|
||||
func Encrypt(plaintext []byte, key []byte) (encrypted []byte, err error) {
|
||||
// generate a random iv each time
|
||||
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
// Section 8.2
|
||||
ivBytes := make([]byte, 12)
|
||||
rand.Read(ivBytes)
|
||||
b, err := aes.NewCipher(e.key)
|
||||
b, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -67,13 +48,9 @@ func (e Encryption) Encrypt(plaintext []byte) (encrypted []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Decrypt an Encryption
|
||||
func (e Encryption) Decrypt(encrypted []byte) (plaintext []byte, err error) {
|
||||
if e.passphrase == nil {
|
||||
plaintext = encrypted
|
||||
return
|
||||
}
|
||||
b, err := aes.NewCipher(e.key)
|
||||
// Decrypt using the pre-generated key
|
||||
func Decrypt(encrypted []byte, key []byte) (plaintext []byte, err error) {
|
||||
b, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,55 +6,42 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkEncryptionNew(b *testing.B) {
|
||||
func BenchmarkEncrypt(b *testing.B) {
|
||||
bob, _, _ := New([]byte("password"), nil)
|
||||
for i := 0; i < b.N; i++ {
|
||||
bob, _ := New([]byte("password"), nil)
|
||||
bob.Encrypt([]byte("hello, world"))
|
||||
Encrypt([]byte("hello, world"), bob)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryption(b *testing.B) {
|
||||
bob, _ := New([]byte("password"), nil)
|
||||
func BenchmarkDecrypt(b *testing.B) {
|
||||
key, _, _ := New([]byte("password"), nil)
|
||||
msg := []byte("hello, world")
|
||||
enc, _ := Encrypt(msg, key)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bob.Encrypt([]byte("hello, world"))
|
||||
Decrypt(enc, key)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryption(t *testing.T) {
|
||||
bob, err := New([]byte("password"), nil)
|
||||
key, salt, err := New([]byte("password"), nil)
|
||||
assert.Nil(t, err)
|
||||
jane, err := New([]byte("password"), bob.Salt())
|
||||
msg := []byte("hello, world")
|
||||
enc, err := Encrypt(msg, key)
|
||||
assert.Nil(t, err)
|
||||
enc, err := bob.Encrypt([]byte("hello, world"))
|
||||
dec, err := Decrypt(enc, key)
|
||||
assert.Nil(t, err)
|
||||
dec, err := jane.Decrypt(enc)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dec, []byte("hello, world"))
|
||||
assert.Equal(t, msg, dec)
|
||||
|
||||
jane2, err := New([]byte("password"), nil)
|
||||
// check reusing the salt
|
||||
key2, _, err := New([]byte("password"), salt)
|
||||
dec, err = Decrypt(enc, key2)
|
||||
assert.Nil(t, err)
|
||||
dec, err = jane2.Decrypt(enc)
|
||||
assert.Equal(t, msg, dec)
|
||||
|
||||
// check reusing the salt
|
||||
key2, _, err = New([]byte("wrong password"), salt)
|
||||
dec, err = Decrypt(enc, key2)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotEqual(t, dec, []byte("hello, world"))
|
||||
|
||||
jane3, err := New([]byte("passwordwrong"), bob.Salt())
|
||||
assert.Nil(t, err)
|
||||
dec, err = jane3.Decrypt(enc)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotEqual(t, dec, []byte("hello, world"))
|
||||
|
||||
}
|
||||
|
||||
func TestNoEncryption(t *testing.T) {
|
||||
bob, err := New(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
jane, err := New(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
enc, err := bob.Encrypt([]byte("hello, world"))
|
||||
assert.Nil(t, err)
|
||||
dec, err := jane.Decrypt(enc)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dec, []byte("hello, world"))
|
||||
assert.Equal(t, enc, []byte("hello, world"))
|
||||
|
||||
assert.NotEqual(t, msg, dec)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func (m Message) String() string {
|
|||
}
|
||||
|
||||
// Send will send out
|
||||
func Send(c *comm.Comm, key crypt.Encryption, m Message) (err error) {
|
||||
func Send(c *comm.Comm, key []byte, m Message) (err error) {
|
||||
mSend, err := Encode(key, m)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -34,21 +34,25 @@ func Send(c *comm.Comm, key crypt.Encryption, m Message) (err error) {
|
|||
}
|
||||
|
||||
// Encode will convert to bytes
|
||||
func Encode(key crypt.Encryption, m Message) (b []byte, err error) {
|
||||
func Encode(key []byte, m Message) (b []byte, err error) {
|
||||
b, err = json.Marshal(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b = compress.Compress(b)
|
||||
b, err = key.Encrypt(b)
|
||||
if key != nil {
|
||||
b, err = crypt.Encrypt(b, key)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Decode will convert from bytes
|
||||
func Decode(key crypt.Encryption, b []byte) (m Message, err error) {
|
||||
b, err = key.Decrypt(b)
|
||||
if err != nil {
|
||||
return
|
||||
func Decode(key []byte, b []byte) (m Message, err error) {
|
||||
if key != nil {
|
||||
b, err = crypt.Decrypt(b, key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
b = compress.Decompress(b)
|
||||
err = json.Unmarshal(b, &m)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
package webrelay
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/models"
|
||||
"github.com/schollz/croc/v6/src/tcp"
|
||||
log "github.com/schollz/logger"
|
||||
)
|
||||
|
||||
func Run(debugString, port string) (err error) {
|
||||
log.SetLevel(debugString)
|
||||
http.HandleFunc("/ws", handlews)
|
||||
http.Handle("/", http.FileServer(http.Dir("html")))
|
||||
log.Infof("running on port %s", port)
|
||||
return http.ListenAndServe(":"+port, nil)
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{} // use default options
|
||||
|
||||
func handlews(w http.ResponseWriter, r *http.Request) {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Debug("upgrade:", err)
|
||||
return
|
||||
}
|
||||
log.Debugf("connected: %+v", c.RemoteAddr())
|
||||
defer c.Close()
|
||||
|
||||
_, message, err := c.ReadMessage()
|
||||
if err != nil {
|
||||
log.Debug("read:", err)
|
||||
return
|
||||
}
|
||||
log.Debugf("recv: %s", message)
|
||||
if bytes.Equal(message, []byte("receive")) {
|
||||
// start receiving
|
||||
log.Debug("initiating reciever")
|
||||
err = receive(c)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Bundle struct {
|
||||
Message string `json:"m,omitempty"`
|
||||
PayloadString string `json:"ps,omitempty"`
|
||||
PayloadBytes []byte `json:"pb,omitempty"`
|
||||
}
|
||||
|
||||
func receive(conn *websocket.Conn) (err error) {
|
||||
conn.WriteMessage(websocket.TextMessage, []byte("initiated"))
|
||||
|
||||
var com *comm.Comm
|
||||
var banner, externalIP, connectPort string
|
||||
for {
|
||||
var message []byte
|
||||
_, message, err = conn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Debug("read:", err)
|
||||
return
|
||||
}
|
||||
var bu Bundle
|
||||
errBundle := json.Unmarshal(message, &bu)
|
||||
if errBundle == nil {
|
||||
if bu.Message == "room" {
|
||||
relayAddress := models.DEFAULT_RELAY
|
||||
relayAddress = "localhost:9009"
|
||||
com, banner, externalIP, err = tcp.ConnectToTCPServer(relayAddress, bu.PayloadString)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
log.Debugf("comm: %+v", com)
|
||||
log.Debugf("banner: %+v", banner)
|
||||
log.Debugf("externalIP: %+v", externalIP)
|
||||
log.Debugf("err: %+v", err)
|
||||
err = com.Send([]byte("ips?"))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
var data []byte
|
||||
data, err = com.Receive()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
log.Debugf("ips data: %s", data)
|
||||
|
||||
err = com.Send([]byte("handshake"))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
connectPort = strings.Split(banner, ",")[0]
|
||||
log.Debugf("connecting on %s", connectPort)
|
||||
err = conn.WriteMessage(websocket.TextMessage, []byte("initpake"))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
b, errBase64 := base64.StdEncoding.DecodeString(string(message))
|
||||
if errBase64 == nil {
|
||||
log.Debug("parsing base64 bytes")
|
||||
err = com.Send(b)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
b, err = com.Receive()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
err = conn.WriteMessage(websocket.TextMessage, []byte(base64.StdEncoding.EncodeToString(b)))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
Loading…
Reference in New Issue