Untested client

pull/2/head
Qian Wang 6 years ago
parent 44d2c0e073
commit 3fd7e01566

@ -0,0 +1,178 @@
// +build go1.11
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"time"
"github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/client/TLS"
mux "github.com/cbeuw/Cloak/internal/multiplex"
"github.com/cbeuw/Cloak/internal/util"
)
var version string
func pipe(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
buf := make([]byte, 20480)
for {
i, err := src.Read(buf)
if err != nil {
go dst.Close()
go src.Close()
return
}
_, err = dst.Write(buf[:i])
if err != nil {
go dst.Close()
go src.Close()
return
}
}
}
// This establishes a connection with ckserver and performs a handshake
func makeRemoteConn(sta *client.State) net.Conn {
d := net.Dialer{Control: protector}
clientHello := TLS.ComposeInitHandshake(sta)
remoteConn, err := d.Dial("tcp", sta.SS_REMOTE_HOST+":"+sta.SS_REMOTE_PORT)
if err != nil {
log.Printf("Connecting to remote: %v\n", err)
return nil
}
_, err = remoteConn.Write(clientHello)
if err != nil {
log.Printf("Sending ClientHello: %v\n", err)
return nil
}
// Three discarded messages: ServerHello, ChangeCipherSpec and Finished
discardBuf := make([]byte, 1024)
for c := 0; c < 3; c++ {
_, err = util.ReadTillDrain(remoteConn, discardBuf)
if err != nil {
log.Printf("Reading discarded message %v: %v\n", c, err)
return nil
}
}
reply := TLS.ComposeReply()
_, err = remoteConn.Write(reply)
if err != nil {
log.Printf("Sending reply to remote: %v\n", err)
return nil
}
return remoteConn
}
func main() {
// Should be 127.0.0.1 to listen to ss-local on this machine
var localHost string
// server_port in ss config, ss sends data on loopback using this port
var localPort string
// The ip of the proxy server
var remoteHost string
// The proxy port,should be 443
var remotePort string
var pluginOpts string
log.SetFlags(log.LstdFlags | log.Lshortfile)
log_init()
if os.Getenv("SS_LOCAL_HOST") != "" {
localHost = os.Getenv("SS_LOCAL_HOST")
localPort = os.Getenv("SS_LOCAL_PORT")
remoteHost = os.Getenv("SS_REMOTE_HOST")
remotePort = os.Getenv("SS_REMOTE_PORT")
pluginOpts = os.Getenv("SS_PLUGIN_OPTIONS")
} else {
localHost = "127.0.0.1"
flag.StringVar(&localPort, "l", "", "localPort: same as server_port in ss config, the plugin listens to SS using this")
flag.StringVar(&remoteHost, "s", "", "remoteHost: IP of your proxy server")
flag.StringVar(&remotePort, "p", "443", "remotePort: proxy port, should be 443")
flag.StringVar(&pluginOpts, "c", "ckclient.json", "pluginOpts: path to ckclient.json or options seperated with semicolons")
askVersion := flag.Bool("v", false, "Print the version number")
printUsage := flag.Bool("h", false, "Print this message")
flag.Parse()
if *askVersion {
fmt.Printf("ck-client %s\n", version)
return
}
if *printUsage {
flag.Usage()
return
}
log.Printf("Starting standalone mode. Listening for ss on %v:%v\n", localHost, localPort)
}
sta := &client.State{
SS_LOCAL_HOST: localHost,
SS_LOCAL_PORT: localPort,
SS_REMOTE_HOST: remoteHost,
SS_REMOTE_PORT: remotePort,
Now: time.Now,
}
err := sta.ParseConfig(pluginOpts)
if err != nil {
log.Fatal(err)
}
if sta.SS_LOCAL_PORT == "" {
log.Fatal("Must specify localPort")
}
if sta.SS_REMOTE_HOST == "" {
log.Fatal("Must specify remoteHost")
}
if sta.TicketTimeHint == 0 {
log.Fatal("TicketTimeHint cannot be empty or 0")
}
initRemoteConn := makeRemoteConn(sta)
obfs := util.MakeObfs(sta.SID)
deobfs := util.MakeDeobfs(sta.SID)
// TODO: where to put obfs deobfs and rtd?
sesh := mux.MakeSession(0, initRemoteConn, obfs, deobfs, util.ReadTillDrain)
for i := 0; i < sta.NumConn-1; i++ {
go func() {
conn := makeRemoteConn(sta)
sesh.AddConnection(conn)
}()
}
listener, err := net.Listen("tcp", sta.SS_LOCAL_HOST+":"+sta.SS_LOCAL_PORT)
if err != nil {
log.Fatal(err)
}
for {
ssConn, err := listener.Accept()
if err != nil {
log.Println(err)
continue
}
go func() {
stream, err := sesh.OpenStream()
if err != nil {
ssConn.Close()
}
go pipe(ssConn, stream)
pipe(stream, ssConn)
}()
}
}

@ -0,0 +1,6 @@
// +build !android
package main
func log_init() {
}

@ -0,0 +1,85 @@
// Copyright 2014 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.
// +build android
package main
/*
To view the log output run:
adb logcat GoLog:I *:S
*/
// Android redirects stdout and stderr to /dev/null.
// As these are common debugging utilities in Go,
// we redirect them to logcat.
//
// Unfortunately, logcat is line oriented, so we must buffer.
/*
#cgo LDFLAGS: -landroid -llog
#include <android/log.h>
#include <stdlib.h>
#include <string.h>
*/
import "C"
import (
"bufio"
"log"
"os"
"unsafe"
)
var (
ctag = C.CString("cloak")
)
type infoWriter struct{}
func (infoWriter) Write(p []byte) (n int, err error) {
cstr := C.CString(string(p))
C.__android_log_write(C.ANDROID_LOG_INFO, ctag, cstr)
C.free(unsafe.Pointer(cstr))
return len(p), nil
}
func lineLog(f *os.File, priority C.int) {
const logSize = 1024 // matches android/log.h.
r := bufio.NewReaderSize(f, logSize)
for {
line, _, err := r.ReadLine()
str := string(line)
if err != nil {
str += " " + err.Error()
}
cstr := C.CString(str)
C.__android_log_write(priority, ctag, cstr)
C.free(unsafe.Pointer(cstr))
if err != nil {
break
}
}
}
func log_init() {
log.SetOutput(infoWriter{})
// android logcat includes all of log.LstdFlags
log.SetFlags(log.Flags() &^ log.LstdFlags)
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
os.Stderr = w
go lineLog(r, C.ANDROID_LOG_ERROR)
r, w, err = os.Pipe()
if err != nil {
panic(err)
}
os.Stdout = w
go lineLog(r, C.ANDROID_LOG_INFO)
}

@ -0,0 +1,9 @@
// +build !android
package main
import "syscall"
func protector(string, string, syscall.RawConn) error {
return nil
}

@ -0,0 +1,121 @@
// +build android
package main
// Stolen from https://github.com/shadowsocks/overture/blob/shadowsocks/core/utils/utils_android.go
/*
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/uio.h>
#define ANCIL_FD_BUFFER(n) \
struct { \
struct cmsghdr h; \
int fd[n]; \
}
int
ancil_send_fds_with_buffer(int sock, const int *fds, unsigned n_fds, void *buffer)
{
struct msghdr msghdr;
char nothing = '!';
struct iovec nothing_ptr;
struct cmsghdr *cmsg;
int i;
nothing_ptr.iov_base = &nothing;
nothing_ptr.iov_len = 1;
msghdr.msg_name = NULL;
msghdr.msg_namelen = 0;
msghdr.msg_iov = &nothing_ptr;
msghdr.msg_iovlen = 1;
msghdr.msg_flags = 0;
msghdr.msg_control = buffer;
msghdr.msg_controllen = sizeof(struct cmsghdr) + sizeof(int) * n_fds;
cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg->cmsg_len = msghdr.msg_controllen;
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
for(i = 0; i < n_fds; i++)
((int *)CMSG_DATA(cmsg))[i] = fds[i];
return(sendmsg(sock, &msghdr, 0) >= 0 ? 0 : -1);
}
int
ancil_send_fd(int sock, int fd)
{
ANCIL_FD_BUFFER(1) buffer;
return(ancil_send_fds_with_buffer(sock, &fd, 1, &buffer));
}
void
set_timeout(int sock)
{
struct timeval tv;
tv.tv_sec = 3;
tv.tv_usec = 0;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval));
}
*/
import "C"
import (
"log"
"syscall"
)
// In Android, once an app starts the VpnService, all outgoing traffic are routed by the system
// to the VPN app. In our case, the VPN app is ss-local. Our outgoing traffic to ck-server
// will be routed back to ss-local which creates an infinite loop.
//
// The Android system provides an API VpnService.protect(int socketFD)
// This tells the system to bypass the socket around the VPN.
func protector(network string, address string, c syscall.RawConn) error {
log.Println("Using Android VPN mode.")
fn := func(s uintptr) {
fd := int(s)
path := "protect_path"
socket, err := syscall.Socket(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
log.Println(err)
return
}
defer syscall.Close(socket)
C.set_timeout(C.int(socket))
err = syscall.Connect(socket, &syscall.SockaddrUnix{Name: path})
if err != nil {
log.Println(err)
return
}
C.ancil_send_fd(C.int(socket), C.int(fd))
dummy := []byte{1}
n, err := syscall.Read(socket, dummy)
if err != nil {
log.Println(err)
return
}
if n != 1 {
log.Println("Failed to protect fd: ", fd)
return
}
}
if err := c.Control(fn); err != nil {
return err
}
return nil
}

@ -0,0 +1,70 @@
package TLS
import (
"encoding/binary"
"github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/util"
"time"
)
type browser interface {
composeExtensions()
composeClientHello()
}
func makeServerName(sta *client.State) []byte {
serverName := sta.ServerName
serverNameListLength := make([]byte, 2)
binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3))
serverNameType := []byte{0x00} // host_name
serverNameLength := make([]byte, 2)
binary.BigEndian.PutUint16(serverNameLength, uint16(len(serverName)))
ret := make([]byte, 2+1+2+len(serverName))
copy(ret[0:2], serverNameListLength)
copy(ret[2:3], serverNameType)
copy(ret[3:5], serverNameLength)
copy(ret[5:], serverName)
return ret
}
func makeNullBytes(length int) []byte {
ret := make([]byte, length)
for i := 0; i < length; i++ {
ret[i] = 0x00
}
return ret
}
// addExtensionRecord, add type, length to extension data
func addExtRec(typ []byte, data []byte) []byte {
length := make([]byte, 2)
binary.BigEndian.PutUint16(length, uint16(len(data)))
ret := make([]byte, 2+2+len(data))
copy(ret[0:2], typ)
copy(ret[2:4], length)
copy(ret[4:], data)
return ret
}
// ComposeInitHandshake composes ClientHello with record layer
func ComposeInitHandshake(sta *client.State) []byte {
var ch []byte
switch sta.MaskBrowser {
case "chrome":
ch = (&chrome{}).composeClientHello(sta)
case "firefox":
ch = (&firefox{}).composeClientHello(sta)
default:
panic("Unsupported browser:" + sta.MaskBrowser)
}
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01})
}
// ComposeReply composes RL+ChangeCipherSpec+RL+Finished
func ComposeReply() []byte {
TLS12 := []byte{0x03, 0x03}
ccsBytes := util.AddRecordLayer([]byte{0x01}, []byte{0x14}, TLS12)
finished := util.PsudoRandBytes(40, time.Now().UnixNano())
fBytes := util.AddRecordLayer(finished, []byte{0x16}, TLS12)
return append(ccsBytes, fBytes...)
}

@ -0,0 +1,82 @@
// Chrome 64
package TLS
import (
"encoding/hex"
"math/rand"
"time"
"github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/util"
)
type chrome struct {
browser
}
func (c *chrome) composeExtensions(sta *client.State) []byte {
// see https://tools.ietf.org/html/draft-davidben-tls-grease-01
// This is exclusive to chrome.
makeGREASE := func() []byte {
rand.Seed(time.Now().UnixNano())
sixteenth := rand.Intn(16)
monoGREASE := byte(sixteenth*16 + 0xA)
doubleGREASE := []byte{monoGREASE, monoGREASE}
return doubleGREASE
}
makeSupportedGroups := func() []byte {
suppGroupListLen := []byte{0x00, 0x08}
ret := make([]byte, 2+8)
copy(ret[0:2], suppGroupListLen)
copy(ret[2:4], makeGREASE())
copy(ret[4:], []byte{0x00, 0x1d, 0x00, 0x17, 0x00, 0x18})
return ret
}
var ext [14][]byte
ext[0] = addExtRec(makeGREASE(), nil) // First GREASE
ext[1] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info
ext[2] = addExtRec([]byte{0x00, 0x00}, makeServerName(sta)) // server name indication
ext[3] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret
ext[4] = addExtRec([]byte{0x00, 0x23}, client.MakeSessionTicket(sta)) // Session tickets
sigAlgo, _ := hex.DecodeString("0012040308040401050308050501080606010201")
ext[5] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
ext[6] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
ext[7] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp
APLN, _ := hex.DecodeString("000c02683208687474702f312e31")
ext[8] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation
ext[9] = addExtRec([]byte{0x75, 0x50}, nil) // channel id
ext[10] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats
ext[11] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups
ext[12] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE
ext[13] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(110-len(ext[2]))) // padding
var ret []byte
for i := 0; i < 14; i++ {
ret = append(ret, ext[i]...)
}
return ret
}
func (c *chrome) composeClientHello(sta *client.State) []byte {
var clientHello [12][]byte
clientHello[0] = []byte{0x01} // handshake type
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
clientHello[2] = []byte{0x03, 0x03} // client version
clientHello[3] = client.MakeRandomField(sta) // random
clientHello[4] = []byte{0x20} // session id length 32
clientHello[5] = util.PsudoRandBytes(32, sta.Now().UnixNano()) // session id
clientHello[6] = []byte{0x00, 0x1c} // cipher suites length 28
cipherSuites, _ := hex.DecodeString("2a2ac02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a")
clientHello[7] = cipherSuites // cipher suites
clientHello[8] = []byte{0x01} // compression methods length 1
clientHello[9] = []byte{0x00} // compression methods
clientHello[10] = []byte{0x01, 0x97} // extensions length 407
clientHello[11] = c.composeExtensions(sta) // extensions
var ret []byte
for i := 0; i < 12; i++ {
ret = append(ret, clientHello[i]...)
}
return ret
}

@ -0,0 +1,57 @@
// Firefox 58
package TLS
import (
"encoding/hex"
"github.com/cbeuw/Cloak/internal/client"
"github.com/cbeuw/Cloak/internal/util"
)
type firefox struct {
browser
}
func (f *firefox) composeExtensions(sta *client.State) []byte {
var ext [10][]byte
ext[0] = addExtRec([]byte{0x00, 0x00}, makeServerName(sta)) // server name indication
ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret
ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info
suppGroup, _ := hex.DecodeString("0008001d001700180019")
ext[3] = addExtRec([]byte{0x00, 0x0a}, suppGroup) // supported groups
ext[4] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats
ext[5] = addExtRec([]byte{0x00, 0x23}, client.MakeSessionTicket(sta)) // Session tickets
APLN, _ := hex.DecodeString("000c02683208687474702f312e31")
ext[6] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation
ext[7] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
sigAlgo, _ := hex.DecodeString("001604030503060308040805080604010501060102030201")
ext[8] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
ext[9] = addExtRec([]byte{0x00, 0x15}, makeNullBytes(121-len(ext[0]))) // padding
var ret []byte
for i := 0; i < 10; i++ {
ret = append(ret, ext[i]...)
}
return ret
}
func (f *firefox) composeClientHello(sta *client.State) []byte {
var clientHello [12][]byte
clientHello[0] = []byte{0x01} // handshake type
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
clientHello[2] = []byte{0x03, 0x03} // client version
clientHello[3] = client.MakeRandomField(sta) // random
clientHello[4] = []byte{0x20} // session id length 32
clientHello[5] = util.PsudoRandBytes(32, sta.Now().UnixNano()) // session id
clientHello[6] = []byte{0x00, 0x1e} // cipher suites length 28
cipherSuites, _ := hex.DecodeString("c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a")
clientHello[7] = cipherSuites // cipher suites
clientHello[8] = []byte{0x01} // compression methods length 1
clientHello[9] = []byte{0x00} // compression methods
clientHello[10] = []byte{0x01, 0x95} // extensions length 405
clientHello[11] = f.composeExtensions(sta) // extensions
var ret []byte
for i := 0; i < 12; i++ {
ret = append(ret, clientHello[i]...)
}
return ret
}

@ -0,0 +1,59 @@
package client
import (
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"github.com/cbeuw/Cloak/internal/util"
"github.com/cbeuw/ecies"
)
func MakeRandomField(sta *State) []byte {
t := make([]byte, 8)
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/12*60*60))
rand := util.PsudoRandBytes(16, sta.Now().UnixNano())
preHash := make([]byte, 56)
copy(preHash[0:32], sta.SID)
copy(preHash[32:40], t)
copy(preHash[40:56], rand)
h := sha256.New()
h.Write(preHash)
ret := make([]byte, 32)
copy(ret[0:16], rand)
copy(ret[16:32], h.Sum(nil)[0:16])
return ret
}
func MakeSessionTicket(sta *State) []byte {
t := make([]byte, 8)
binary.BigEndian.PutUint64(t, uint64(sta.Now().Unix()/int64(sta.TicketTimeHint)))
plain := make([]byte, 40)
copy(plain, sta.SID)
copy(plain[32:], t)
// With the default settings (P256, AES128, SHA256) of the ecies package, len(ct)==153.
//
// ciphertext is composed of 3 parts: marshalled X and Y coordinates on the curve,
// iv+ciphertext of the block cipher (aes128 in this case),
// and the hmac which is 32 bytes because it's sha256
//
// The marshalling is done by crypto/elliptic.Marshal. According to the code,
// the size after marshall is 65
//
// IV is 16 bytes. The size of ciphertext is equal to the plaintext, which is 40,
// that is 32 bytes of SID + 8 bytes of timestamp/tickettimehint.
// 16+40 = 56
//
// Then the hmac is 32 bytes
//
// 65+56+32=153
ct, _ := ecies.Encrypt(rand.Reader, sta.Pub, plain, nil, nil)
sessionTicket := make([]byte, 192)
// The reason for ct[1:] is that, the first byte of ct is always 0x04
// This is specified in the section 4.3.6 of ANSI X9.62 (the uncompressed form).
// This is a flag that is useless to us and it will expose our pattern
// (because the sessionTicket isn't fully random anymore). Therefore we drop it.
copy(sessionTicket, ct[1:])
copy(sessionTicket[152:], util.PsudoRandBytes(40, sta.Now().UnixNano()))
return sessionTicket
}

@ -0,0 +1,110 @@
package client
import (
"crypto/elliptic"
"encoding/base64"
"encoding/json"
"errors"
"github.com/cbeuw/ecies"
"io/ioutil"
"strings"
"time"
)
type rawConfig struct {
ServerName string
Key string
TicketTimeHint int
MaskBrowser string
NumConn int
}
// State stores global variables
type State struct {
SS_LOCAL_HOST string
SS_LOCAL_PORT string
SS_REMOTE_HOST string
SS_REMOTE_PORT string
Now func() time.Time
SID []byte
Pub *ecies.PublicKey
TicketTimeHint int
ServerName string
MaskBrowser string
NumConn int
}
// semi-colon separated value. This is for Android plugin options
func ssvToJson(ssv string) (ret []byte) {
unescape := func(s string) string {
r := strings.Replace(s, "\\\\", "\\", -1)
r = strings.Replace(r, "\\=", "=", -1)
r = strings.Replace(r, "\\;", ";", -1)
return r
}
lines := strings.Split(unescape(ssv), ";")
ret = []byte("{")
for _, ln := range lines {
if ln == "" {
break
}
sp := strings.SplitN(ln, "=", 2)
key := sp[0]
value := sp[1]
// JSON doesn't like quotation marks around int
// Yes this is extremely ugly but it's still better than writing a tokeniser
if key == "TicketTimeHint" || key == "NumConn" {
ret = append(ret, []byte("\""+key+"\":"+value+",")...)
} else {
ret = append(ret, []byte("\""+key+"\":\""+value+"\",")...)
}
}
ret = ret[:len(ret)-1] // remove the last comma
ret = append(ret, '}')
return ret
}
// ParseConfig parses the config (either a path to json or Android config) into a State variable
func (sta *State) ParseConfig(conf string) (err error) {
var content []byte
if strings.Contains(conf, ";") && strings.Contains(conf, "=") {
content = ssvToJson(conf)
} else {
content, err = ioutil.ReadFile(conf)
if err != nil {
return err
}
}
var preParse rawConfig
err = json.Unmarshal(content, &preParse)
if err != nil {
return err
}
sta.ServerName = preParse.ServerName
sta.TicketTimeHint = preParse.TicketTimeHint
sta.MaskBrowser = preParse.MaskBrowser
sid, pub, err := parseKey(preParse.Key)
if err != nil {
return errors.New("Failed to parse Key: " + err.Error())
}
sta.SID = sid
sta.Pub = pub
return nil
}
func parseKey(b64 string) ([]byte, *ecies.PublicKey, error) {
b, err := base64.StdEncoding.DecodeString(b64)
if err != nil {
return nil, nil, err
}
sid := b[0:32]
marshalled := b[32:]
x, y := elliptic.Unmarshal(ecies.DefaultCurve, marshalled)
pub := &ecies.PublicKey{
X: x,
Y: y,
Curve: ecies.DefaultCurve,
Params: ecies.ParamsFromCurve(ecies.DefaultCurve),
}
return sid, pub, nil
}

@ -3,8 +3,8 @@ package multiplex
import ()
type Frame struct {
StreamID uint32
Seq uint32
ClosedStreamID uint32
Payload []byte
StreamID uint32
Seq uint32
ClosingStreamID uint32
Payload []byte
}

@ -7,8 +7,9 @@ import (
const (
// Copied from smux
errBrokenPipe = "broken pipe"
acceptBacklog = 1024
errBrokenPipe = "broken pipe"
errRepeatStreamClosing = "trying to close a closed stream"
acceptBacklog = 1024
closeBacklog = 512
)
@ -41,19 +42,27 @@ type Session struct {
}
// TODO: put this in main maybe?
func MakeSession(id int, conns []net.Conn) *Session {
// 1 conn is needed to make a session
func MakeSession(id int, conn net.Conn, obfs func(*Frame) []byte, deobfs func([]byte) *Frame, obfsedReader func(net.Conn, []byte) (int, error)) *Session {
sesh := &Session{
id: id,
obfs: obfs,
deobfs: deobfs,
obfsedReader: obfsedReader,
nextStreamID: 0,
streams: make(map[uint32]*Stream),
acceptCh: make(chan *Stream, acceptBacklog),
closeQCh: make(chan uint32, closeBacklog),
}
sesh.sb = makeSwitchboard(conns, sesh)
sesh.sb = makeSwitchboard(conn, sesh)
sesh.sb.run()
return sesh
}
func (sesh *Session) AddConnection(conn net.Conn) {
sesh.sb.newConnCh <- conn
}
func (sesh *Session) OpenStream() (*Stream, error) {
sesh.nextStreamIDM.Lock()
id := sesh.nextStreamID

@ -29,6 +29,9 @@ type Stream struct {
nextSendSeqM sync.Mutex
nextSendSeq uint32
closingM sync.Mutex
closing bool
}
func makeStream(id uint32, sesh *Session) *Stream {
@ -73,9 +76,9 @@ func (stream *Stream) Write(in []byte) (n int, err error) {
}
f := &Frame{
StreamID: stream.id,
Seq: stream.nextSendSeq,
ClosedStreamID: closingID,
StreamID: stream.id,
Seq: stream.nextSendSeq,
ClosingStreamID: closingID,
}
copy(f.Payload, in)
@ -91,6 +94,13 @@ func (stream *Stream) Write(in []byte) (n int, err error) {
}
func (stream *Stream) Close() error {
// Because closing a closed channel causes panic
stream.closingM.Lock()
defer stream.closingM.Unlock()
if stream.closing {
return errors.New(errRepeatStreamClosing)
}
stream.closing = true
stream.session.delStream(stream.id)
close(stream.die)
close(stream.sortedBufCh)

@ -18,6 +18,7 @@ type switchboard struct {
// For telling dispatcher how many bytes have been sent after Connection.send.
sentNotifyCh chan *sentNotifier
dispatCh chan []byte
newConnCh chan net.Conn
}
// Some data comes from a Stream to be sent through one of the many
@ -43,28 +44,27 @@ func (a byQ) Less(i, j int) bool {
return a[i].sendQueue < a[j].sendQueue
}
func makeSwitchboard(conns []net.Conn, sesh *Session) *switchboard {
// It takes at least 1 conn to start a switchboard
func makeSwitchboard(conn net.Conn, sesh *Session) *switchboard {
sb := &switchboard{
session: sesh,
ces: []*connEnclave{},
sentNotifyCh: make(chan *sentNotifier, sentNotifyBacklog),
dispatCh: make(chan []byte, dispatchBacklog),
}
for _, c := range conns {
ce := &connEnclave{
sb: sb,
remoteConn: c,
sendQueue: 0,
}
sb.ces = append(sb.ces, ce)
ce := &connEnclave{
sb: sb,
remoteConn: conn,
sendQueue: 0,
}
sb.ces = append(sb.ces, ce)
return sb
}
func (sb *switchboard) run() {
go startDispatcher()
go startDeplexer()
go sb.startDispatcher()
go sb.startDeplexer()
}
// Everytime after a remoteConn sends something, it constructs this struct
@ -86,6 +86,7 @@ func (ce *connEnclave) send(data []byte) {
}
// Dispatcher sends data coming from a stream to a remote connection
// I used channels here because I didn't want to use mutex
func (sb *switchboard) startDispatcher() {
for {
select {
@ -96,6 +97,14 @@ func (sb *switchboard) startDispatcher() {
case notified := <-sb.sentNotifyCh:
notified.ce.sendQueue -= notified.sent
sort.Sort(byQ(sb.ces))
case conn := <-sb.newConnCh:
newCe := &connEnclave{
sb: sb,
remoteConn: conn,
sendQueue: 0,
}
sb.ces = append(sb.ces, newCe)
sort.Sort(byQ(sb.ces))
}
}
}
@ -111,6 +120,7 @@ func (sb *switchboard) startDeplexer() {
if !sb.session.isStream(frame.StreamID) {
sb.session.addStream(frame.StreamID)
}
sb.session.getStream(frame.ClosingStreamID).Close()
sb.session.getStream(frame.StreamID).recvNewFrame(frame)
}
}()

@ -0,0 +1,72 @@
package util
import (
"crypto/aes"
"crypto/cipher"
"encoding/binary"
mux "github.com/cbeuw/Cloak/internal/multiplex"
)
func encrypt(iv []byte, key []byte, plaintext []byte) []byte {
block, _ := aes.NewCipher(key)
ciphertext := make([]byte, len(plaintext))
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext, plaintext)
return ciphertext
}
func decrypt(iv []byte, key []byte, ciphertext []byte) []byte {
ret := make([]byte, len(ciphertext))
copy(ret, ciphertext) // Because XORKeyStream is inplace, but we don't want the input to be changed
block, _ := aes.NewCipher(key)
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ret, ret)
// ret is now plaintext
return ret
}
func MakeObfs(key []byte) func(*mux.Frame) []byte {
obfs := func(f *mux.Frame) []byte {
header := make([]byte, 12)
binary.BigEndian.PutUint32(header[0:4], f.StreamID)
binary.BigEndian.PutUint32(header[4:8], f.Seq)
binary.BigEndian.PutUint32(header[8:12], f.ClosingStreamID)
// header: [StreamID 4 bytes][Seq 4 bytes][ClosingStreamID 4 bytes]
plaintext := make([]byte, 12+len(f.Payload)-16)
copy(plaintext[0:12], header)
copy(plaintext[12:], f.Payload[16:])
// plaintext: [header 12 bytes][Payload[16:]]
iv := f.Payload[0:16]
ciphertext := encrypt(iv, key, plaintext)
obfsed := make([]byte, 16+len(ciphertext))
copy(obfsed[0:16], iv)
copy(obfsed[16:], ciphertext)
// obfsed: [iv 16 bytes][ciphertext]
ret := AddRecordLayer(obfsed, []byte{0x17}, []byte{0x03, 0x03})
return ret
}
return obfs
}
func MakeDeobfs(key []byte) func([]byte) *mux.Frame {
deobfs := func(in []byte) *mux.Frame {
peeled := PeelRecordLayer(in)
plaintext := decrypt(peeled[0:16], key, peeled[16:])
// plaintext: [header 12 bytes][Payload[16:]]
streamID := binary.BigEndian.Uint32(plaintext[0:4])
seq := binary.BigEndian.Uint32(plaintext[4:8])
closingStreamID := binary.BigEndian.Uint32(plaintext[8:12])
payload := make([]byte, len(plaintext)-12)
copy(payload[0:16], peeled[0:16])
copy(payload[16:], plaintext[12:])
ret := &mux.Frame{
StreamID: streamID,
Seq: seq,
ClosingStreamID: closingStreamID,
Payload: payload,
}
return ret
}
return deobfs
}

@ -0,0 +1,105 @@
package util
import (
"crypto/rand"
"encoding/binary"
"errors"
"io"
"math/big"
prand "math/rand"
"net"
"time"
)
// BtoInt converts a byte slice into int in Big Endian order
// Uint methods from binary package can be used, but they are messy
func BtoInt(b []byte) int {
var mult uint = 1
var sum uint
length := uint(len(b))
var i uint
for i = 0; i < length; i++ {
sum += uint(b[i]) * (mult << ((length - i - 1) * 8))
}
return int(sum)
}
// CryptoRandBytes generates a byte slice filled with cryptographically secure random bytes
func CryptoRandBytes(length int) []byte {
byteMax := big.NewInt(int64(256))
ret := make([]byte, length)
for i := 0; i < length; i++ {
randInt, _ := rand.Int(rand.Reader, byteMax)
randByte := byte(randInt.Int64())
ret[i] = randByte
}
return ret
}
// PsudoRandBytes returns a byte slice filled with psudorandom bytes generated by the seed
func PsudoRandBytes(length int, seed int64) []byte {
prand.Seed(seed)
ret := make([]byte, length)
for i := 0; i < length; i++ {
randByte := byte(prand.Intn(256))
ret[i] = randByte
}
return ret
}
// ReadTillDrain reads TLS data according to its record layer
func ReadTillDrain(conn net.Conn, buffer []byte) (n int, err error) {
// TCP is a stream. Multiple TLS messages can arrive at the same time,
// a single message can also be segmented due to MTU of the IP layer.
// This function guareentees a single TLS message to be read and everything
// else is left in the buffer.
i, err := io.ReadFull(conn, buffer[:5])
if err != nil {
return
}
dataLength := BtoInt(buffer[3:5])
left := dataLength
readPtr := 5
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
for left != 0 {
if readPtr > len(buffer) || readPtr+left > len(buffer) {
err = errors.New("Reading TLS message: actual size greater than header's specification")
return
}
// If left > buffer size (i.e. our message got segmented), the entire MTU is read
// if left = buffer size, the entire buffer is all there left to read
// if left < buffer size (i.e. multiple messages came together),
// only the message we want is read
i, err = io.ReadFull(conn, buffer[readPtr:readPtr+left])
if err != nil {
return
}
left -= i
readPtr += i
}
conn.SetReadDeadline(time.Time{})
n = 5 + dataLength
buffer = buffer[:n]
return
}
// AddRecordLayer adds record layer to data
func AddRecordLayer(input []byte, typ []byte, ver []byte) []byte {
length := make([]byte, 2)
binary.BigEndian.PutUint16(length, uint16(len(input)))
ret := make([]byte, 5+len(input))
copy(ret[0:1], typ)
copy(ret[1:3], ver)
copy(ret[3:5], length)
copy(ret[5:], input)
return ret
}
// PeelRecordLayer peels off the record layer
func PeelRecordLayer(data []byte) []byte {
ret := data[5:]
return ret
}
Loading…
Cancel
Save