mirror of https://github.com/cbeuw/Cloak
Untested client
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 = ¬hing;
|
||||
nothing_ptr.iov_len = 1;
|
||||
msghdr.msg_name = NULL;
|
||||
msghdr.msg_namelen = 0;
|
||||
msghdr.msg_iov = ¬hing_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
|
||||
}
|
@ -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…
Reference in New Issue