initial part works

This commit is contained in:
Zack Scholl 2019-04-29 19:50:01 -07:00
parent 972dce1ec5
commit ac113dfe47
5 changed files with 136 additions and 228 deletions

View File

@ -152,7 +152,7 @@ func send(c *cli.Context) (err error) {
IsSender: true, IsSender: true,
Debug: c.GlobalBool("debug"), Debug: c.GlobalBool("debug"),
NoPrompt: c.GlobalBool("yes"), NoPrompt: c.GlobalBool("yes"),
AddressRelay: c.GlobalString("relay"), RelayAddress: c.GlobalString("relay"),
Stdout: c.GlobalBool("stdout"), Stdout: c.GlobalBool("stdout"),
}) })
if err != nil { if err != nil {
@ -187,7 +187,7 @@ func receive(c *cli.Context) (err error) {
IsSender: false, IsSender: false,
Debug: c.GlobalBool("debug"), Debug: c.GlobalBool("debug"),
NoPrompt: c.GlobalBool("yes"), NoPrompt: c.GlobalBool("yes"),
AddressRelay: c.GlobalString("relay"), RelayAddress: c.GlobalString("relay"),
Stdout: c.GlobalBool("stdout"), Stdout: c.GlobalBool("stdout"),
}) })
if err != nil { if err != nil {

View File

@ -6,7 +6,6 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -16,18 +15,18 @@ import (
log "github.com/cihub/seelog" log "github.com/cihub/seelog"
"github.com/denisbrodbeck/machineid" "github.com/denisbrodbeck/machineid"
"github.com/pkg/errors"
"github.com/schollz/croc/v6/src/comm" "github.com/schollz/croc/v6/src/comm"
"github.com/schollz/croc/v6/src/crypt" "github.com/schollz/croc/v6/src/crypt"
"github.com/schollz/croc/v6/src/logger" "github.com/schollz/croc/v6/src/logger"
"github.com/schollz/croc/v6/src/message"
"github.com/schollz/croc/v6/src/tcp"
"github.com/schollz/croc/v6/src/utils" "github.com/schollz/croc/v6/src/utils"
"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"
) )
const BufferSize = 4096 * 10
const Channels = 1
func init() { func init() {
logger.SetLogLevel("debug") logger.SetLogLevel("debug")
} }
@ -44,7 +43,8 @@ type Options struct {
IsSender bool IsSender bool
SharedSecret string SharedSecret string
Debug bool Debug bool
AddressRelay string RelayAddress string
RelayPorts []string
Stdout bool Stdout bool
NoPrompt bool NoPrompt bool
} }
@ -52,6 +52,7 @@ type Options struct {
type Client struct { type Client struct {
Options Options Options Options
Pake *pake.Pake Pake *pake.Pake
Key crypt.Encryption
// steps involved in forming relationship // steps involved in forming relationship
Step1ChannelSecured bool Step1ChannelSecured bool
@ -68,8 +69,8 @@ type Client struct {
CurrentFile *os.File CurrentFile *os.File
CurrentFileChunks []int64 CurrentFileChunks []int64
// tcp connectios // tcp connections
conn [17]*comm.Comm conn []*comm.Comm
bar *progressbar.ProgressBar bar *progressbar.ProgressBar
spinner *spinner.Spinner spinner *spinner.Spinner
@ -79,13 +80,6 @@ type Client struct {
quit chan bool quit chan bool
} }
type Message struct {
Type string `json:"t,omitempty"`
Message string `json:"m,omitempty"`
Bytes []byte `json:"b,omitempty"`
Num int `json:"n,omitempty"`
}
type Chunk struct { type Chunk struct {
Bytes []byte `json:"b,omitempty"` Bytes []byte `json:"b,omitempty"`
Location int64 `json:"l,omitempty"` Location int64 `json:"l,omitempty"`
@ -112,11 +106,6 @@ type SenderInfo struct {
FilesToTransfer []FileInfo FilesToTransfer []FileInfo
} }
func (m Message) String() string {
b, _ := json.Marshal(m)
return string(b)
}
// New establishes a new connection for transfering files between two instances. // New establishes a new connection for transfering files between two instances.
func New(ops Options) (c *Client, err error) { func New(ops Options) (c *Client, err error) {
c = new(Client) c = new(Client)
@ -126,6 +115,22 @@ func New(ops Options) (c *Client, err error) {
Debug(c.Options.Debug) Debug(c.Options.Debug)
log.Debugf("options: %+v", c.Options) log.Debugf("options: %+v", c.Options)
log.Debug("establishing connection")
c.conn = make([]*comm.Comm, len(c.Options.RelayPorts))
// connect to the relay for messaging
c.conn[0], err = tcp.ConnectToTCPServer(fmt.Sprintf("%s:%s", c.Options.RelayAddress, c.Options.RelayPorts[0]), c.Options.SharedSecret)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("could not connect to %s:%s", c.Options.RelayAddress, c.Options.RelayPorts[0]))
return
}
log.Debugf("connection established: %+v", c.conn[0])
// use default key (no encryption, until PAKE succeeds)
c.Key, err = crypt.New(nil, nil)
if err != nil {
return
}
// initialize pake // initialize pake
if c.Options.IsSender { 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, elliptic.P521(), 1*time.Microsecond)
@ -140,6 +145,7 @@ func New(ops Options) (c *Client, err error) {
return return
} }
// TransferOptions for sending
type TransferOptions struct { type TransferOptions struct {
PathToFiles []string PathToFiles []string
KeepPathInRemote bool KeepPathInRemote bool
@ -156,6 +162,8 @@ func (c *Client) Receive() (err error) {
} }
func (c *Client) transfer(options TransferOptions) (err error) { func (c *Client) transfer(options TransferOptions) (err error) {
// connect to the server
if c.Options.IsSender { if c.Options.IsSender {
c.FilesToTransfer = make([]FileInfo, len(options.PathToFiles)) c.FilesToTransfer = make([]FileInfo, len(options.PathToFiles))
totalFilesSize := int64(0) totalFilesSize := int64(0)
@ -224,9 +232,9 @@ func (c *Client) transfer(options TransferOptions) (err error) {
c.machineID = machID c.machineID = machID
fmt.Fprintf(os.Stderr, "Sending %s (%s) from your machine, '%s'\n", fname, utils.ByteCountDecimal(totalFilesSize), machID) fmt.Fprintf(os.Stderr, "Sending %s (%s) from your machine, '%s'\n", fname, utils.ByteCountDecimal(totalFilesSize), machID)
fmt.Fprintf(os.Stderr, "Code is: %s\nOn the other computer run\n\ncroc %s\n", c.Options.SharedSecret, c.Options.SharedSecret) fmt.Fprintf(os.Stderr, "Code is: %s\nOn the other computer run\n\ncroc %s\n", c.Options.SharedSecret, c.Options.SharedSecret)
c.spinner.Suffix = " waiting for recipient..." // // c.spinner.Suffix = " waiting for recipient..."
} }
c.spinner.Start() // c.spinner.Start()
// create channel for quitting // create channel for quitting
// quit with c.quit <- true // quit with c.quit <- true
c.quit = make(chan bool) c.quit = make(chan bool)
@ -234,10 +242,10 @@ func (c *Client) transfer(options TransferOptions) (err error) {
// if recipient, initialize with sending pake information // if recipient, initialize with sending pake information
log.Debug("ready") log.Debug("ready")
if !c.Options.IsSender && !c.Step1ChannelSecured { if !c.Options.IsSender && !c.Step1ChannelSecured {
err = c.redisdb.Publish(c.nameOutChannel, Message{ err = message.Send(c.conn[0], c.Key, message.Message{
Type: "pake", Type: "pake",
Bytes: c.Pake.Bytes(), Bytes: c.Pake.Bytes(),
}.String()).Err() })
if err != nil { if err != nil {
return return
} }
@ -245,70 +253,63 @@ func (c *Client) transfer(options TransferOptions) (err error) {
// listen for incoming messages and process them // listen for incoming messages and process them
for { for {
select { var data []byte
case <-c.quit: data, err = c.conn[0].Receive()
return
case msg := <-c.incomingMessageChannel:
var m Message
err = json.Unmarshal([]byte(msg.Payload), &m)
if err != nil { if err != nil {
return return
} }
if m.Type == "finished" { err = c.processMessage(data)
err = c.redisdb.Publish(c.nameOutChannel, Message{
Type: "finished",
}.String()).Err()
return err
}
err = c.processMessage(m)
if err != nil { if err != nil {
return return
} }
default:
time.Sleep(1 * time.Millisecond)
}
} }
return return
} }
func (c *Client) processMessage(m Message) (err error) { func (c *Client) processMessage(payload []byte) (err error) {
m, err := message.Decode(c.Key, payload)
if err != nil {
return
}
switch m.Type { switch m.Type {
case "pake": case "pake":
if c.spinner.Suffix != " performing PAKE..." { // if // c.spinner.Suffix != " performing PAKE..." {
c.spinner.Stop() // // c.spinner.Stop()
c.spinner.Suffix = " performing PAKE..." // // c.spinner.Suffix = " performing PAKE..."
c.spinner.Start() // // c.spinner.Start()
} // }
notVerified := !c.Pake.IsVerified() notVerified := !c.Pake.IsVerified()
err = c.Pake.Update(m.Bytes) err = c.Pake.Update(m.Bytes)
if err != nil { if err != nil {
return return
} }
if (notVerified && c.Pake.IsVerified() && !c.Options.IsSender) || !c.Pake.IsVerified() { if (notVerified && c.Pake.IsVerified() && !c.Options.IsSender) || !c.Pake.IsVerified() {
err = c.redisdb.Publish(c.nameOutChannel, Message{ err = message.Send(c.conn[0], c.Key, message.Message{
Type: "pake", Type: "pake",
Bytes: c.Pake.Bytes(), Bytes: c.Pake.Bytes(),
}.String()).Err() })
} }
if c.Pake.IsVerified() { if c.Pake.IsVerified() {
log.Debug(c.Pake.SessionKey()) log.Debug("session key is verified, generating encryption")
key, err := c.Pake.SessionKey()
if err != nil {
return err
}
c.Key, err = crypt.New(key, []byte(c.Options.SharedSecret))
if err != nil {
return err
}
c.Step1ChannelSecured = true c.Step1ChannelSecured = true
} }
case "error": case "error":
c.spinner.Stop() // c.spinner.Stop()
fmt.Print("\r") fmt.Print("\r")
err = fmt.Errorf("peer error: %s", m.Message) err = fmt.Errorf("peer error: %s", m.Message)
return err return err
case "fileinfo": case "fileinfo":
var senderInfo SenderInfo var senderInfo SenderInfo
var decryptedBytes []byte err = json.Unmarshal(m.Bytes, &senderInfo)
key, _ := c.Pake.SessionKey()
decryptedBytes, err = crypt.DecryptFromBytes(m.Bytes, key)
if err != nil {
log.Error(err)
return
}
err = json.Unmarshal(decryptedBytes, &senderInfo)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return return
@ -322,14 +323,14 @@ func (c *Client) processMessage(m Message) (err error) {
for _, fi := range c.FilesToTransfer { for _, fi := range c.FilesToTransfer {
totalSize += fi.Size totalSize += fi.Size
} }
c.spinner.Stop() // c.spinner.Stop()
if !c.Options.NoPrompt { if !c.Options.NoPrompt {
fmt.Fprintf(os.Stderr, "\rAccept %s (%s) from machine '%s'? (y/n) ", fname, utils.ByteCountDecimal(totalSize), senderInfo.MachineID) fmt.Fprintf(os.Stderr, "\rAccept %s (%s) from machine '%s'? (y/n) ", fname, utils.ByteCountDecimal(totalSize), senderInfo.MachineID)
if strings.ToLower(strings.TrimSpace(utils.GetInput(""))) != "y" { if strings.ToLower(strings.TrimSpace(utils.GetInput(""))) != "y" {
err = c.redisdb.Publish(c.nameOutChannel, Message{ err = message.Send(c.conn[0], c.Key, message.Message{
Type: "error", Type: "error",
Message: "refusing files", Message: "refusing files",
}.String()).Err() })
return fmt.Errorf("refused files") return fmt.Errorf("refused files")
} }
} else { } else {
@ -339,68 +340,22 @@ func (c *Client) processMessage(m Message) (err error) {
c.Step2FileInfoTransfered = true c.Step2FileInfoTransfered = true
case "recipientready": case "recipientready":
var remoteFile RemoteFileRequest var remoteFile RemoteFileRequest
var decryptedBytes []byte err = json.Unmarshal(m.Bytes, &remoteFile)
key, _ := c.Pake.SessionKey()
decryptedBytes, err = crypt.DecryptFromBytes(m.Bytes, key)
if err != nil {
log.Error(err)
return
}
err = json.Unmarshal(decryptedBytes, &remoteFile)
if err != nil { if err != nil {
return return
} }
c.FilesToTransferCurrentNum = remoteFile.FilesToTransferCurrentNum c.FilesToTransferCurrentNum = remoteFile.FilesToTransferCurrentNum
c.CurrentFileChunks = remoteFile.CurrentFileChunks c.CurrentFileChunks = remoteFile.CurrentFileChunks
c.Step3RecipientRequestFile = true c.Step3RecipientRequestFile = true
case "datachannel-offer":
err = c.dataChannelReceive()
if err != nil {
return
}
err = c.recvSess.SetSDP(m.Message)
if err != nil {
return
}
var answer string
answer, err = c.recvSess.CreateAnswer()
if err != nil {
return
}
// Output the answer in base64 so we can paste it in browser
err = c.redisdb.Publish(c.nameOutChannel, Message{
Type: "datachannel-answer",
Message: answer,
Num: m.Num,
}.String()).Err()
// start receiving data
pathToFile := path.Join(c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote, c.FilesToTransfer[c.FilesToTransferCurrentNum].Name)
c.spinner.Stop()
key, _ := c.Pake.SessionKey()
c.recvSess.ReceiveData(pathToFile, c.FilesToTransfer[c.FilesToTransferCurrentNum].Size, key)
log.Debug("sending close-sender")
err = c.redisdb.Publish(c.nameOutChannel, Message{
Type: "close-sender",
}.String()).Err()
case "datachannel-answer":
log.Debug("got answer:", m.Message)
// Apply the answer as the remote description
err = c.sendSess.SetSDP(m.Message)
pathToFile := path.Join(c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderSource, c.FilesToTransfer[c.FilesToTransferCurrentNum].Name)
c.spinner.Stop()
fmt.Fprintf(os.Stderr, "\r\nTransfering...\n")
key, _ := c.Pake.SessionKey()
c.sendSess.TransferFile(pathToFile, key)
case "close-sender": case "close-sender":
log.Debug("close-sender received...") log.Debug("close-sender received...")
c.Step4FileTransfer = false c.Step4FileTransfer = false
c.Step3RecipientRequestFile = false c.Step3RecipientRequestFile = false
c.sendSess.StopSending()
log.Debug("sending close-recipient") log.Debug("sending close-recipient")
err = c.redisdb.Publish(c.nameOutChannel, Message{ err = message.Send(c.conn[0], c.Key, message.Message{
Type: "close-recipient", Type: "close-recipient",
Num: m.Num, Num: m.Num,
}.String()).Err() })
case "close-recipient": case "close-recipient":
c.Step4FileTransfer = false c.Step4FileTransfer = false
c.Step3RecipientRequestFile = false c.Step3RecipientRequestFile = false
@ -424,11 +379,10 @@ func (c *Client) updateState() (err error) {
log.Error(err) log.Error(err)
return return
} }
key, _ := c.Pake.SessionKey() err = message.Send(c.conn[0], c.Key, message.Message{
err = c.redisdb.Publish(c.nameOutChannel, Message{
Type: "fileinfo", Type: "fileinfo",
Bytes: crypt.EncryptToBytes(b, key), Bytes: b,
}.String()).Err() })
if err != nil { if err != nil {
return return
} }
@ -456,9 +410,9 @@ func (c *Client) updateState() (err error) {
if finished { if finished {
// TODO: do the last finishing stuff // TODO: do the last finishing stuff
log.Debug("finished") log.Debug("finished")
err = c.redisdb.Publish(c.nameOutChannel, Message{ err = message.Send(c.conn[0], c.Key, message.Message{
Type: "finished", Type: "finished",
}.String()).Err() })
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -472,107 +426,24 @@ func (c *Client) updateState() (err error) {
CurrentFileChunks: c.CurrentFileChunks, CurrentFileChunks: c.CurrentFileChunks,
FilesToTransferCurrentNum: c.FilesToTransferCurrentNum, FilesToTransferCurrentNum: c.FilesToTransferCurrentNum,
}) })
key, _ := c.Pake.SessionKey() err = message.Send(c.conn[0], c.Key, message.Message{
err = c.redisdb.Publish(c.nameOutChannel, Message{
Type: "recipientready", Type: "recipientready",
Bytes: crypt.EncryptToBytes(bRequest, key), Bytes: bRequest,
}.String()).Err() })
if err != nil { if err != nil {
return return
} }
c.Step3RecipientRequestFile = true c.Step3RecipientRequestFile = true
err = c.dataChannelReceive() // TODO: receive
} }
if c.Options.IsSender && c.Step3RecipientRequestFile && !c.Step4FileTransfer { if c.Options.IsSender && c.Step3RecipientRequestFile && !c.Step4FileTransfer {
log.Debug("start sending data!") log.Debug("start sending data!")
err = c.dataChannelSend() // TODO: send
c.Step4FileTransfer = true c.Step4FileTransfer = true
} }
return return
} }
func (c *Client) dataChannelReceive() (err error) {
c.recvSess = receiver.NewWith(receiver.Config{})
err = c.recvSess.CreateConnection()
if err != nil {
return
}
c.recvSess.CreateDataHandler()
return
}
func (c *Client) dataChannelSend() (err error) {
c.sendSess = sender.NewWith(sender.Config{
Configuration: common.Configuration{
OnCompletion: func() {
},
},
})
if err := c.sendSess.CreateConnection(); err != nil {
log.Error(err)
return err
}
if err := c.sendSess.CreateDataChannel(); err != nil {
log.Error(err)
return err
}
offer, err := c.sendSess.CreateOffer()
if err != nil {
log.Error(err)
return err
}
// sending offer
err = c.redisdb.Publish(c.nameOutChannel, Message{
Type: "datachannel-offer",
Message: offer,
}.String()).Err()
if err != nil {
return
}
return
}
// MissingChunks returns the positions of missing chunks.
// If file doesn't exist, it returns an empty chunk list (all chunks).
// If the file size is not the same as requested, it returns an empty chunk list (all chunks).
func MissingChunks(fname string, fsize int64, chunkSize int) (chunks []int64) {
fstat, err := os.Stat(fname)
if fstat.Size() != fsize {
return
}
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
buffer := make([]byte, chunkSize)
emptyBuffer := make([]byte, chunkSize)
chunkNum := 0
chunks = make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize))))
var currentLocation int64
for {
bytesread, err := f.Read(buffer)
if err != nil {
break
}
if bytes.Equal(buffer[:bytesread], emptyBuffer[:bytesread]) {
chunks[chunkNum] = currentLocation
}
currentLocation += int64(bytesread)
}
if chunkNum == 0 {
chunks = []int64{}
} else {
chunks = chunks[:chunkNum]
}
return
}
// Encode encodes the input in base64 // Encode encodes the input in base64
// It can optionally zip the input before encoding // It can optionally zip the input before encoding
func Encode(obj interface{}) string { func Encode(obj interface{}) string {

View File

@ -3,9 +3,9 @@ package message
import ( import (
"encoding/json" "encoding/json"
"github.com/schollz/croc/v6/src/comm"
"github.com/schollz/croc/v6/src/compress" "github.com/schollz/croc/v6/src/compress"
"github.com/schollz/croc/v6/src/crypt" "github.com/schollz/croc/v6/src/crypt"
"github.com/schollz/croc/v6/src/comm"
) )
// Message is the possible payload for messaging // Message is the possible payload for messaging
@ -22,8 +22,8 @@ func (m Message) String() string {
} }
// Send will send out // Send will send out
func Send(c *comm.Comm, m Message) (err error) { func Send(c *comm.Comm, key crypt.Encryption, m Message) (err error) {
mSend, err := Encode(m) mSend, err := Encode(key, m)
if err != nil { if err != nil {
return return
} }
@ -32,27 +32,22 @@ return
} }
// Encode will convert to bytes // Encode will convert to bytes
func Encode(m Message, e ...crypt.Encryption) (b []byte, err error) { func Encode(key crypt.Encryption, m Message) (b []byte, err error) {
b, err = json.Marshal(m) b, err = json.Marshal(m)
if err != nil { if err != nil {
return return
} }
b = compress.Compress(b) b = compress.Compress(b)
if len(e) > 0 { b, err = key.Encrypt(b)
b, err = e[0].Encrypt(b)
}
return return
} }
// Decode will convert from bytes // Decode will convert from bytes
func Decode(b []byte, e ...crypt.Encryption) (m Message, err error) { func Decode(key crypt.Encryption, b []byte) (m Message, err error) {
if len(e) > 0 { b, err = key.Decrypt(b)
b, err = e[0].Decrypt(b)
if err != nil { if err != nil {
return return
} }
}
b = compress.Decompress(b) b = compress.Decompress(b)
err = json.Unmarshal(b, &m) err = json.Unmarshal(b, &m)
return return

View File

@ -10,14 +10,14 @@ import (
func TestMessage(t *testing.T) { func TestMessage(t *testing.T) {
m := Message{Type: "message", Message: "hello, world"} m := Message{Type: "message", Message: "hello, world"}
e, err := crypt.New([]byte("passphrase"), nil) e, err := crypt.New(nil, nil)
assert.Nil(t, err) assert.Nil(t, err)
fmt.Println(e.Salt()) fmt.Println(e.Salt())
b, err := Encode(m, e) b, err := Encode(e, m)
assert.Nil(t, err) assert.Nil(t, err)
fmt.Printf("%x\n", b) fmt.Printf("%x\n", b)
m2, err := Decode(b, e) m2, err := Decode(e, b)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, m, m2) assert.Equal(t, m, m2)
} }

View File

@ -9,7 +9,9 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"bytes"
"net" "net"
"math"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -109,3 +111,43 @@ func ByteCountDecimal(b int64) string {
} }
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp]) return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
} }
// MissingChunks returns the positions of missing chunks.
// If file doesn't exist, it returns an empty chunk list (all chunks).
// If the file size is not the same as requested, it returns an empty chunk list (all chunks).
func MissingChunks(fname string, fsize int64, chunkSize int) (chunks []int64) {
fstat, err := os.Stat(fname)
if fstat.Size() != fsize {
return
}
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
buffer := make([]byte, chunkSize)
emptyBuffer := make([]byte, chunkSize)
chunkNum := 0
chunks = make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize))))
var currentLocation int64
for {
bytesread, err := f.Read(buffer)
if err != nil {
break
}
if bytes.Equal(buffer[:bytesread], emptyBuffer[:bytesread]) {
chunks[chunkNum] = currentLocation
}
currentLocation += int64(bytesread)
}
if chunkNum == 0 {
chunks = []int64{}
} else {
chunks = chunks[:chunkNum]
}
return
}