Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Iorpim committed Dec 21, 2020
0 parents commit eadf1e9
Show file tree
Hide file tree
Showing 11 changed files with 734,675 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

received/*
test.go
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${file}",
"env": {},
"args": ["-v", "4", "ws://localhost:8989/api", "test2.txt"]
}
]
}
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# WScp


Do you often need to transfer files around but is constantly harassed by annoying restrictive proxies?

So this project is for you!

*WScp* is a state-of-the-art file transfer utility, it uses encrypted websocket communication to hide the fact that you are doing something you probably shouldn't.

Example usage:
```
$ go run main.go -p http://user:[email protected] -a -v 2 ws://site.web/api test.txt
..:: WScp ::..
[+] Sending: test.txt
[+] Destination: ws://site.web/api
- Connecting to ws://site.web/api
- Sending handshake message
- Received valid handshake
- Received valid AES key
- Sending init message
- Received valid encrypted response
- Handshake completed
10.50 Mb - 42 pkt |##################################################| 42 pkt - 10.50 Mb
```

Yes, it does come with a progress bar, free of charge

---
#### To do
##### Project
- [ ] Add build scripts
- [ ] Add install scripts
- [ ] Improve documentation
##### Server
- [ ] Add server config file
- [ ] Add server flags
- [ ] Improve server logging
- [ ] Add endpoint for file retrieval
##### Client
- [ ] Add client config file
- [ ] Add file download option
##### Transfer Package
- [ ] Improve binary marshaling in order to reduce footprint
##### Both
- [ ] Implement sync request for continuing failed transfers
- [ ] Make better comments
182 changes: 182 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package main

import (
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"

"./transfer"
"./transfer/packet"

"github.com/gorilla/websocket"
)

var verbose int
var proxy string
var stride int

var ascii bool

func parseArgs() (string, string) {
flag.StringVar(&proxy, "p", "", "Enable proxy passthrough.\nFormat: {scheme}://[user[:password]@]{address}[:port]")
flag.IntVar(&verbose, "v", 0, "Enables verbose output.")
flag.IntVar(&stride, "s", 256*1024, "Defines the packet size")
flag.BoolVar(&ascii, "a", false, "Makes progress bar output ASCII only")
flag.Parse()

if flag.NArg() < 2 {
fmt.Println("Missing arguments!")
log.Fatalf("Usage: %s [-h] [-p {scheme}://[user[:password]@]{address}[:port]] [-v {n}] [-s {n}] [-a] {address} {file}\n", filepath.Base(os.Args[0]))
}

//fmt.Println(flag.Args())
//fmt.Println(proxy, verbose)
return flag.Arg(0), flag.Arg(1)
}

func debugln(s string, i int) {
if verbose >= i {
fmt.Println(s)
}
}

func parseURL(addr string) *url.URL {
u, err := url.Parse(addr)
if err != nil {
debugln(fmt.Sprintf(" - Address: %s", addr), 2)
log.Fatal("URL PARSING ERROR: ", err)
}
//{scheme: "ws", Host: addr, Path: "/"}
if u.Scheme == "" {
u.Scheme = "ws"
}

return u
}

func connect(u *url.URL, p *url.URL) *websocket.Conn {
var dialer *websocket.Dialer
var h http.Header
if u.User != nil {
credentials, _ := url.PathUnescape(u.User.String())
h = http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte(credentials))}}
u.User = nil
} else {
h = nil
}

debugln(fmt.Sprintf(" - Connecting to %s", u.String()), 1)

if p != nil {
dialer = &websocket.Dialer{
Proxy: http.ProxyURL(p),
}
} else {
dialer = websocket.DefaultDialer
}

c, r, err := dialer.Dial(u.String(), h)
if err != nil {
debugln(string(r.StatusCode), 4)
body, _ := ioutil.ReadAll(r.Body)
debugln(string(body), 4)
// TODO: Play dial up sound when connecting
log.Fatal("Failed to dial address!\nError: ", err)
}
return c
}

func handshakeConnection(c *websocket.Conn, t *transfer.Transfer) {
handshake := packet.HandshakeMessage{PubKey: t.RSA.Bytes()}

p := packet.Packet{Type: packet.Handshake}
p.AddContent(handshake)
debugln(" - Sending handshake message", 1)
c.WriteMessage(websocket.BinaryMessage, p.Encode())

_, msg, _ := c.ReadMessage()
res := packet.New(t.Decrypt(msg))
b, _ := res.ParseContent()
debugln(" - Received response", 3)
h := b.(packet.HandshakeMessage)
debugln(" - Received valid handshake", 2)
t.RSA.SetSymmetricKey(h.Key)
debugln(" - Received valid AES key", 2)
debugln(" - Initiating packet processing", 3)
t.InitPackets()

init := packet.InitMessage{Size: t.Size, Filename: t.Filename, Checksum: t.Checksum, PacketCount: t.PacketCount, Index: t.Index}
p.Type = packet.Init
p.AddContent(init)
debugln(" - Sending init message", 2)
c.WriteMessage(websocket.BinaryMessage, t.Encrypt(p.Encode()))

_, msg, _ = c.ReadMessage()
debugln(" - Received response", 3)
res = packet.New(t.Decrypt(msg))
debugln(" - Received valid encrypted response", 2)
if res.Type != packet.Ack {
log.Panicln("Handshake failed\nReceived type: ", res.Type)
}
debugln(" - Received ACK", 3)
debugln(" - Handshake completed", 2)
}

func sendFile(c *websocket.Conn, t *transfer.Transfer) {
fmt.Println("")
for pkt := range t.Packets {
debugln(fmt.Sprintf(" - Sending packet n° %d", t.Index), 3)
t.Replay = pkt
c.WriteMessage(websocket.BinaryMessage, pkt)
t.PrintProgress(50, ascii)

_, msg, _ := c.ReadMessage()
res := packet.New(t.Decrypt(msg))
for res.Type == packet.Replay {
debugln(" - Replay request received", 1)
c.WriteMessage(websocket.BinaryMessage, t.Replay)

_, msg, _ = c.ReadMessage()
res = packet.New(t.Decrypt(msg))
}
if res.Type != packet.Ack {
log.Fatalln("Invalid message type received\nType: ", res.Type)
}
debugln(" - Received ACK", 4)
}
fmt.Println("")
}

func removeCreds(u *url.URL) string {
return fmt.Sprintf("%s://%s%s", u.Scheme, u.Host, u.Path)
}

func main() {
fmt.Println(" ..:: WScp ::.. ")
addr, filename := parseArgs()
u := parseURL(addr)
fmt.Printf("[+] Sending: %s\n", filename)
var p *url.URL
if proxy != "" {
p = parseURL(proxy)
fmt.Printf("[+] Proxy: %s\n", removeCreds(p))
} else {
p = nil
}
fmt.Printf("[+] Destination: %s\n\n", removeCreds(u))
c := connect(u, p)
defer c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))

t := transfer.New(filename, 1024)
t.UpdateStride(stride)
handshakeConnection(c, t)
sendFile(c, t)

//done := make(chan struct{})
}
129 changes: 129 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// +build ignore

package main

import (
"io"
"log"
"net/http"
"os"

"./transfer"
"./transfer/packet"
"github.com/gorilla/websocket"
)

func checkHash(fd *os.File, checksum string) (bool, bool) {
offset, err := fd.Seek(0, io.SeekCurrent)
if err != nil {
log.Fatalln("Failed to get current file offset while calculating file hash\nError: ", err)
}
_, err = fd.Seek(0, io.SeekStart)
if err != nil {
log.Fatalln("Failed to seek file start while calculating file hash\nError: ", err)
}
hash := transfer.HashFile(fd)
_, err = fd.Seek(offset, io.SeekStart)
if err != nil {
log.Println("Failed to seek file offset while calculating file hash\nError: ", err)
}
//debugln(fmt.Sprintf("Calculated file hash: %s", hash), 3)

return hash == checksum, err != nil
}

func transferData(h http.ResponseWriter, r *http.Request) {
//log.Println("Connection received from %s", addr)
upgrader := websocket.Upgrader{}
c, err := upgrader.Upgrade(h, r, nil)
if err != nil {
log.Println("Failed to upgrade connection\nError: ", err)
return
}
defer c.Close()
//debugln("Upgraded connection", 2)

mt, msg, err := c.ReadMessage()
if err != nil {
log.Panicln("Failed to read message\nError: ", err)
return
}
//debugln(fmt.Sprintf("Read message %u", msg), 5)
p := packet.New(msg)
b, _ := p.ParseContent()
hs := b.(packet.HandshakeMessage)
t := transfer.Handshake(&hs)

p.Type = packet.Handshake
p.AddContent(packet.HandshakeMessage{
Key: t.RSA.GenerateSymmetricKey(),
})
c.WriteMessage(mt, t.PubKey.PubEncrypt(p.Encode()))

mt, msg, err = c.ReadMessage()
p = packet.New(t.Decrypt(msg))
b, _ = p.ParseContent()
init := b.(packet.InitMessage)
t.Init(&init, "received")
defer t.Fd.Close()

p.Type = packet.Ack
p.AddContent(packet.AckMessage{})
c.WriteMessage(mt, t.Encrypt(p.Encode()))
log.Println("Handshake complete")
log.Print(t)
cache := 0

for {
mt, msg, err := c.ReadMessage()
if err != nil {
if e, c := err.(*websocket.CloseError); c && e.Code == 1000 {
log.Println("Transfer session complete")
break
}
log.Println("Error: ", err)
break
}
p = packet.New(t.Decrypt(msg))
b, _ = p.ParseContent()
content := b.(packet.ContentMessage)
/*hash := sha256.New() // Not really necessary, the crypto layer already does integrity checking
if base64.StdEncoding.EncodeToString(hash.Sum(content.Content)) != content.Checksum {
p := packet.Packet{
Type: packet.Replay,
}
p.AddContent(packet.ReplayMessage{})
c.WriteMessage(mt, t.Encrypt(p.Encode()))
i--
continue
}*/
n, err := t.Fd.Write(content.Content)
if err != nil {
log.Fatalln("Failed to write to file\nError: ", err)
}
// If cache > 75Mb, commit changes to disk
if cache += n; cache > 78643200 {
t.Fd.Sync()
cache = 0
}

p := packet.Packet{
Type: packet.Ack,
}
p.AddContent(packet.AckMessage{})
c.WriteMessage(mt, t.Encrypt(p.Encode()))
}
log.Println("Finished writing to file")
if matching, _ := checkHash(t.Fd, t.Checksum); matching {
log.Println("Matching file hash found, file integrity confirmed")
} else {
log.Println("Failed to match file hash, new transfer required")
}
}

func main() {
addr := "127.0.0.1:8989"
//http.HandleFunc("/", serve)
http.HandleFunc("/api", transferData)
http.ListenAndServe(addr, nil)
}
1 change: 1 addition & 0 deletions test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello, World!
Loading

0 comments on commit eadf1e9

Please sign in to comment.